feat: 合并项目成绩并优化检录顺序展示
This commit is contained in:
parent
989f9edd18
commit
3d63badbc8
@ -13,10 +13,12 @@
|
||||
<aside class="schedule-panel">
|
||||
<div class="section-title"><span class="section-icon"><el-icon><Calendar /></el-icon></span><div><small>ORDER OF EVENTS</small><h2>赛事检录顺序</h2></div></div>
|
||||
<div class="stage-list">
|
||||
<div v-for="(stage,index) in projectStages" :key="stage.eventProjectId" :class="['stage-item',`status-${stage.stageStatus}`]">
|
||||
<div class="stage-index"><span>{{ String(index + 1).padStart(2,'0') }}</span></div>
|
||||
<div v-if="hiddenStageCount.before" class="stage-overflow stage-overflow-top">上方还有 {{ hiddenStageCount.before }} 个项目</div>
|
||||
<div v-for="stage in visibleProjectStages" :key="stage.eventProjectId" :class="['stage-item',`status-${stage.stageStatus}`,{current:stage._isCurrent}]">
|
||||
<div class="stage-index"><span>{{ String(stage._sequenceIndex + 1).padStart(2,'0') }}</span></div>
|
||||
<div class="stage-copy"><strong>{{ stage.projectName || '未命名项目' }}</strong><span>{{ stage.checkedUnits }} / {{ stage.totalUnits }} 已检录</span><em><i></i>{{ stage.stageStatusText || stageStatusLabel(stage.stageStatus) }}</em></div>
|
||||
</div>
|
||||
<div v-if="hiddenStageCount.after" class="stage-overflow stage-overflow-bottom">下方还有 {{ hiddenStageCount.after }} 个项目</div>
|
||||
<div v-if="!projectStages.length" class="empty-block"><el-icon><Calendar /></el-icon><b>暂无检录安排</b><span>等待后台发布检录场次</span></div>
|
||||
</div>
|
||||
<div class="schedule-foot"><span></span><p>请参赛人员提前做好准备<br>按现场引导有序检录</p></div>
|
||||
@ -95,6 +97,22 @@ const projectStages=computed(()=>{
|
||||
return {...stage,stageStatus:status,stageStatusText:stageStatusLabel(status)};
|
||||
});
|
||||
});
|
||||
const visibleStageLimit=5;
|
||||
const currentStageIndex=computed(()=>{
|
||||
const currentProjectId=currentCheckin.value?.eventProjectId;
|
||||
const exact=projectStages.value.findIndex(stage=>currentProjectId&&String(stage.eventProjectId)===String(currentProjectId));
|
||||
return exact>=0?exact:projectStages.value.findIndex(stage=>Number(stage.stageStatus)===1);
|
||||
});
|
||||
const visibleStageRange=computed(()=>{
|
||||
const total=projectStages.value.length;
|
||||
if(total<=visibleStageLimit)return {start:0,end:total};
|
||||
const focus=currentStageIndex.value>=0?currentStageIndex.value:0;
|
||||
const half=Math.floor(visibleStageLimit/2);
|
||||
const start=Math.max(0,Math.min(focus-half,total-visibleStageLimit));
|
||||
return {start,end:start+visibleStageLimit};
|
||||
});
|
||||
const visibleProjectStages=computed(()=>projectStages.value.slice(visibleStageRange.value.start,visibleStageRange.value.end).map((stage,index)=>({...stage,_sequenceIndex:visibleStageRange.value.start+index,_isCurrent:visibleStageRange.value.start+index===currentStageIndex.value})));
|
||||
const hiddenStageCount=computed(()=>({before:visibleStageRange.value.start,after:Math.max(0,projectStages.value.length-visibleStageRange.value.end)}));
|
||||
const unitsPerPage=15;
|
||||
const unitPageCount=computed(()=>Math.max(1,Math.ceil((data.value.units||[]).length/unitsPerPage)));
|
||||
const visibleUnits=computed(()=>{const all=data.value.units||[];const page=unitPage.value%unitPageCount.value;return all.slice(page*unitsPerPage,page*unitsPerPage+unitsPerPage)});
|
||||
@ -110,7 +128,13 @@ async function load(){if(!eventId||requesting)return;requesting=true;try{const r
|
||||
function toggleFullscreen(){if(fullscreenDocument.fullscreenElement)fullscreenDocument.exitFullscreen?.().catch(()=>{});else fullscreenDocument.documentElement.requestFullscreen?.({navigationUI:'hide'}).catch(()=>{});storesTagsViewRoutes.setCurrenFullscreen(true)}
|
||||
function requestAutoFullscreen(){if(route.query.autofullscreen==='1'&&!document.fullscreenElement)document.documentElement.requestFullscreen?.({navigationUI:'hide'}).catch(()=>{})}
|
||||
function showControls(){controlsVisible.value=true;window.clearTimeout(controlTimer);controlTimer=window.setTimeout(()=>controlsVisible.value=false,2600)}
|
||||
function setPreview(){if(!route.query.preview)return;const names=['凌予蓁','刘姝妍','乔韵宁','廖歆悦','程沐晞','陈禹舟','韩蕙阳','孟清和','廖翊博','张景泽','任家驹','周瑾瑜','张锦川','施宜程','苏栩宁','林若瑶','陈星宇','徐梓涵','吴一诺','陆子昂'];data.value={eventName:'2026年上海城市业余联赛跳绳联赛(杨浦站)',serverTimeText:'2026-07-29 10:05:32',weekDayText:'星期三',stages:[{eventProjectId:1,groupId:1,projectName:'3分钟10人长绳“8”字跳',groupName:'少儿男子组',order:1,totalUnits:10,checkedUnits:6,stageStatus:1},{eventProjectId:1,groupId:2,projectName:'3分钟10人长绳“8”字跳',groupName:'少儿女子组',order:11,totalUnits:10,checkedUnits:4,stageStatus:1},{eventProjectId:2,groupId:3,projectName:'两人同步规定套路',groupName:'少儿组',order:21,totalUnits:8,checkedUnits:0,stageStatus:2},{eventProjectId:3,groupId:4,projectName:'个人花样基础套路赛',groupName:'儿童组',order:29,totalUnits:12,checkedUnits:0,stageStatus:3}],currentCheckin:{eventProjectId:1,projectName:'3分钟10人长绳“8”字跳',displayName:'3分钟10人长绳“8”字跳'},currentCompeting:{displayName:'个人花样基础套路赛 · 儿童组',unitName:'跃动之夏培训学校有限公司',teamName:'诺宝宝花样跳绳队',athleteNames:['张嘉果','王灵曦','英轩诚']},units:names.map((name,index)=>({unitKey:String(index),groupName:index<10?'少儿男子组':'少儿女子组',athleteNames:[name],teamName:`第 ${index+1} 参赛队`,checkinStatus:index<10?2:1,checkinStatusText:index<10?'已检录':'待检录'}))};syncClock()}
|
||||
function setPreview(){
|
||||
if(!route.query.preview)return;
|
||||
const names=['凌予蓁','刘姝妍','乔韵宁','廖歆悦','程沐晞','陈禹舟','韩蕙阳','孟清和','廖翊博','张景泽','任家驹','周瑾瑜','张锦川','施宜程','苏栩宁','林若瑶','陈星宇','徐梓涵','吴一诺','陆子昂'];
|
||||
const projectNames=['30秒单摇跳','30秒双摇跳','一分钟并脚跳','两人同步规定套路','个人花样基础套路赛','3分钟10人长绳“8”字跳','车轮跳规定套路','交互绳速度赛','团体花样赛'];
|
||||
data.value={eventName:'2026年上海城市业余联赛跳绳联赛(杨浦站)',serverTimeText:'2026-07-29 10:05:32',weekDayText:'星期三',stages:projectNames.map((projectName,index)=>({eventProjectId:index+1,projectName,order:index*10+1,totalUnits:20,checkedUnits:index<5?20:index===5?10:0,stageStatus:index<5?4:index===5?1:index===6?2:3})),currentCheckin:{eventProjectId:6,projectName:projectNames[5],displayName:projectNames[5]},currentCompeting:{displayName:'个人花样基础套路赛 · 儿童组',unitName:'跃动之夏培训学校有限公司',teamName:'诺宝宝花样跳绳队',athleteNames:['张嘉果','王灵曦','英轩诚']},units:names.map((name,index)=>({unitKey:String(index),groupName:index<10?'少儿男子组':'少儿女子组',athleteNames:[name],teamName:`第 ${index+1} 参赛队`,checkinStatus:index<10?2:1,checkinStatusText:index<10?'已检录':'待检录'}))};
|
||||
syncClock();
|
||||
}
|
||||
onMounted(async()=>{storesTagsViewRoutes.setCurrenFullscreen(true);setPreview();if(!route.query.preview)await load();await nextTick();resize();window.addEventListener('resize',resize);pollTimer=window.setInterval(()=>document.visibilityState==='visible'&&!route.query.preview&&load(),3000);clockTimer=window.setInterval(tick,1000);unitTimer=window.setInterval(()=>{unitPage.value=(unitPage.value+1)%unitPageCount.value},9000);requestAutoFullscreen()});
|
||||
onBeforeUnmount(()=>{[pollTimer,clockTimer,unitTimer].forEach(window.clearInterval);window.clearTimeout(controlTimer);window.removeEventListener('resize',resize)});
|
||||
</script>
|
||||
@ -124,5 +148,5 @@ onBeforeUnmount(()=>{[pollTimer,clockTimer,unitTimer].forEach(window.clearInterv
|
||||
.bottom-line{position:absolute;left:34px;right:34px;bottom:15px;display:flex;align-items:center;gap:16px;color:#326b9f;font-size:11px;letter-spacing:.15em}.bottom-line span{height:1px;flex:1;background:linear-gradient(90deg,transparent,#174d81)}.bottom-line span:last-child{transform:scaleX(-1)}.controls{position:absolute;right:22px;bottom:23px;z-index:8;display:flex;align-items:center;gap:8px;padding:7px;border:1px solid rgba(78,204,255,.2);border-radius:12px;background:rgba(2,15,60,.84);backdrop-filter:blur(10px);opacity:0;transform:translateY(8px);transition:.25s;pointer-events:none}.controls.visible{opacity:1;transform:none;pointer-events:auto}.controls button{display:flex;align-items:center;gap:6px;padding:8px 12px;border:1px solid rgba(67,173,237,.25);border-radius:8px;color:#9ddffc;background:rgba(12,75,145,.35);cursor:pointer}.controls>span{display:flex;align-items:center;gap:7px;padding:0 9px;color:#52e9b9;font-size:11px}.controls>span.offline{color:#ff7e86}
|
||||
@keyframes pulse{50%{opacity:.35;box-shadow:0 0 15px currentColor}}@keyframes card-in{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}
|
||||
.schedule-panel{display:flex;flex-direction:column;padding:24px 22px;border:1px solid rgba(57,179,255,.28);border-radius:18px;background:linear-gradient(160deg,rgba(11,52,143,.62),rgba(2,20,77,.72));box-shadow:inset 0 1px rgba(119,224,255,.12),0 20px 50px rgba(0,0,26,.18);overflow:hidden}.schedule-panel .section-title small{display:block;color:#4d92cd;font-size:11px;font-weight:700;letter-spacing:.2em}.schedule-panel h2{margin:4px 0 0;font-size:24px}
|
||||
.schedule-panel .stage-list{display:flex;flex-direction:column}.schedule-panel .stage-item{width:100%;flex:none}
|
||||
.schedule-panel .stage-list{position:relative;display:flex;flex-direction:column;justify-content:center}.schedule-panel .stage-item{width:100%;flex:none;transition:filter .3s ease,opacity .3s ease,transform .3s ease}.schedule-panel .stage-item:not(.current){opacity:.72}.schedule-panel .stage-item.current{filter:drop-shadow(0 0 12px rgba(43,226,177,.14));transform:translateX(4px)}.schedule-panel .stage-item.current .stage-copy strong{color:#fff}.stage-overflow{height:22px;flex:none;color:#4f86b7;font-size:10px;letter-spacing:.08em;text-align:center}.stage-overflow-top{margin-bottom:5px}.stage-overflow-bottom{margin-top:-1px}.stage-overflow::before,.stage-overflow::after{content:"";display:inline-block;width:34px;height:1px;margin:0 8px 3px;background:linear-gradient(90deg,transparent,#2772a8)}.stage-overflow::after{transform:scaleX(-1)}
|
||||
</style>
|
||||
|
||||
@ -10,13 +10,14 @@
|
||||
<div class="workspace">
|
||||
<aside class="session-panel">
|
||||
<div class="session-head"><b>比赛场次</b><el-button text type="primary" :loading="loadingSessions" @click="loadSessions">刷新</el-button></div>
|
||||
<el-input v-model="sessionKeyword" clearable placeholder="搜索场次" @keyup.enter="loadSessions" />
|
||||
<el-input v-model="sessionKeyword" clearable placeholder="搜索项目或场次" @keyup.enter="loadSessions" />
|
||||
<el-select v-model="sessionStatus" clearable placeholder="场次状态" style="width:100%;margin-top:8px" @change="loadSessions"><el-option v-for="x in sessionStatuses" :key="x.value" :label="x.label" :value="x.value" /></el-select>
|
||||
<div class="session-list">
|
||||
<button v-for="(session, index) in sessions" :key="session.sessionId" :class="{ active: selectedSession?.sessionId === session.sessionId }" @click="selectSession(session)">
|
||||
<div><strong>{{ session.projectName || '未命名项目' }}</strong><span>{{ formatSessionName(session, index) }} · {{ session.groupName || '混合组别' }} / {{ session.venueName }}</span></div>
|
||||
<em :class="`status-${session.sessionStatus}`">{{ session.sessionStatusName }}</em>
|
||||
<i><span :style="{ width: progress(session) + '%' }"></span></i><small>{{ session.completedEntries }}/{{ session.totalEntries }}</small>
|
||||
<button v-for="(project, projectIndex) in sessionGroups" :key="project.key" class="project-switch" :class="{ active: selectedProjectId === project.key }" @click="selectProject(project)">
|
||||
<span class="project-sequence">{{ String(projectIndex + 1).padStart(2, '0') }}</span>
|
||||
<div><strong>{{ project.projectName }}</strong><span>{{ project.sessions.length }} 个场次 · {{ project.groupCount }} 个组别</span></div>
|
||||
<em :class="`status-${project.sessionStatus}`">{{ project.sessionStatusName }}</em>
|
||||
<i><span :style="{ width: progress(project) + '%' }"></span></i><small>{{ project.completedEntries }}/{{ project.totalEntries }}</small>
|
||||
</button>
|
||||
<el-empty v-if="!loadingSessions && !sessions.length" :image-size="70" description="暂无场次" />
|
||||
</div>
|
||||
@ -29,8 +30,8 @@
|
||||
<div class="control-field group-field"><label>项目 / 组别 / 场次</label><el-select :model-value="selectedSession?.sessionId" placeholder="请选择需要控制的组别" filterable @change="selectSessionById"><el-option v-for="(session,index) in sessions" :key="session.sessionId" :label="`${session.projectName} / ${session.groupName || '混合组别'} / ${formatSessionName(session,index)}`" :value="session.sessionId"/></el-select></div>
|
||||
<div class="control-buttons"><el-button @click="openScoreScreen">显示成绩大屏</el-button><el-button class="checkin-button" @click="openCheckinScreen">切换检录大屏</el-button><el-button type="primary" :disabled="!selectedSession" @click="openRanking">投出{{ castEntityLabel }}排名</el-button></div>
|
||||
</section>
|
||||
<div v-if="selectedSession" class="result-heading">
|
||||
<div><b>{{ selectedSession.projectName || '未命名项目' }}</b><span>{{ selectedSession.groupName || '混合组别' }}</span><small>场次 {{ selectedSession.sessionNo }} · {{ selectedSession.venueName }}</small></div>
|
||||
<div v-if="selectedProject" class="result-heading">
|
||||
<div><b>{{ selectedProject.projectName }}</b><span>{{ selectedProject.groupCount }} 个组别</span><small>合并展示 {{ selectedProject.sessions.length }} 个场次 · 按秩序册顺序排列</small></div>
|
||||
<div class="entry-count"><b>{{ entries.length }}</b><small>当前列表</small></div>
|
||||
</div>
|
||||
<div class="filterbar">
|
||||
@ -49,9 +50,10 @@
|
||||
|
||||
<div class="table-wrap"><el-table ref="workbenchTableRef" v-loading="loadingEntries" :data="entries" row-key="sessionEntryId" height="100%" border stripe scrollbar-always-on class="workbench-table score-matrix-table" highlight-current-row @selection-change="onSelection">
|
||||
<el-table-column v-if="activeTab === 'results'" type="selection" width="44" fixed="left" :selectable="canConfirm" :reserve-selection="true" />
|
||||
<el-table-column label="场次" width="78" fixed="left" align="center"><template #default>{{ selectedSessionDisplay }}</template></el-table-column>
|
||||
<el-table-column label="场地" width="76" fixed="left" align="center"><template #default>{{ selectedSession?.venueName || '—' }}</template></el-table-column>
|
||||
<el-table-column label="运动员姓名" min-width="132" fixed="left"><template #default="{ row }"><strong class="athlete-name">{{ row.teamName || row.participantName }}</strong><small class="subtext">{{ row.entryNo }} · {{ row.groupName }}</small></template></el-table-column>
|
||||
<el-table-column label="场次" width="78" fixed="left" align="center"><template #default="{ row }">{{ rowSessionDisplay(row) }}</template></el-table-column>
|
||||
<el-table-column label="场地" width="76" fixed="left" align="center"><template #default="{ row }">{{ row._session?.venueName || '—' }}</template></el-table-column>
|
||||
<el-table-column label="组别" min-width="112" fixed="left"><template #default="{ row }"><span class="group-cell">{{ row.groupName || row._session?.groupName || '混合组别' }}</span></template></el-table-column>
|
||||
<el-table-column label="运动员姓名" min-width="132" fixed="left"><template #default="{ row }"><strong class="athlete-name">{{ row.teamName || row.participantName }}</strong><small class="subtext">{{ row.entryNo }}</small></template></el-table-column>
|
||||
<el-table-column label="单位" min-width="180" fixed="left"><template #default="{ row }"><span class="unit-name">{{ row.unitName || row.teamName || '—' }}</span></template></el-table-column>
|
||||
<el-table-column v-for="group in judgeMatrixGroups" :key="group.key" :label="group.label" align="center" header-align="center">
|
||||
<el-table-column v-for="judge in group.judges" :key="judge.key" :label="judge.label" width="112" align="center">
|
||||
@ -64,7 +66,6 @@
|
||||
<el-table-column label="规定分" width="88" align="center"><template #default="{ row }">{{ score(row.result?.requiredScore) }}</template></el-table-column>
|
||||
<el-table-column label="扣分" width="78" align="center"><template #default="{ row }">{{ score(row.result?.deductionScore) }}</template></el-table-column>
|
||||
<el-table-column label="总分" width="96" align="center"><template #default="{ row }"><b class="total" :class="{ dq: row.result?.isDisqualified }">{{ row.result?.isDisqualified ? 'DQ' : score(row.result?.totalScore ?? row.result?.scoreText) }}</b></template></el-table-column>
|
||||
<el-table-column label="排名" width="72" fixed="right" align="center"><template #default="{ row }"><b :class="['rank-number', `rank-${row.result?.rank || 0}`]">{{ row.result?.rank || '—' }}</b></template></el-table-column>
|
||||
<el-table-column label="状态" width="96" fixed="right" align="center"><template #default="{ row }"><el-tag v-if="row.result" :type="resultTag(row.result.resultStatus)" effect="light">{{ row.result.resultStatusName }}</el-tag><span v-else class="muted">计算中</span></template></el-table-column>
|
||||
<el-table-column label="操作" :width="activeTab === 'screen' ? 190 : 250" fixed="right" align="center"><template #default="{ row }">
|
||||
<div v-if="activeTab === 'screen'" class="row-actions"><el-tooltip :content="castDisabledReason(row)" :disabled="canCastForKind(row)"><span><el-button size="small" type="primary" :loading="actionLoading && rowActionId === row.sessionEntryId" :disabled="!canCastForKind(row)" @click="castScore(row)">投出{{ castEntityLabel }}成绩</el-button></span></el-tooltip><el-button size="small" @click="showDetail(row)">评分详情</el-button></div>
|
||||
@ -111,30 +112,46 @@ let savedContext={};try{savedContext=JSON.parse(sessionStorage.getItem('scoring-
|
||||
const eventId=ref(Number(route.query.eventId)||Number(savedContext.eventId)||0);
|
||||
const eventName=ref(String(route.query.eventName||savedContext.eventName||'未选择赛事'));
|
||||
if(eventId.value)sessionStorage.setItem('scoring-context',JSON.stringify({...savedContext,eventId:eventId.value,eventName:eventName.value,mode:'freestyle'}));
|
||||
const activeTab=ref('screen'), sessions=ref([]), selectedSession=ref(null), entries=ref([]), currentCast=ref(null);
|
||||
const activeTab=ref('screen'), sessions=ref([]), selectedProjectId=ref(''), selectedSession=ref(null), entries=ref([]), currentCast=ref(null);
|
||||
const loadingSessions=ref(false), loadingEntries=ref(false), offline=ref(false), sessionKeyword=ref(''), sessionStatus=ref(null), entryKeyword=ref(''), resultStatus=ref(null), selectedResultIds=ref([]), selectedEntryIds=ref([]);
|
||||
const workbenchTableRef=ref(null), batchConfirmDialogOpen=ref(false);
|
||||
const detailVisible=ref(false), detailRow=ref(null), rankingVisible=ref(false), reasonVisible=ref(false), reasonAction=ref(''), reasonText=ref(''), reasonRow=ref(null), actionLoading=ref(false), rowActionId=ref(null);
|
||||
const correctionVisible=ref(false), correctionRow=ref(null), correctionForm=ref({judgeRoleType:null,scoreSheetId:null,reason:''});
|
||||
const corrections=ref([]), loadingCorrections=ref(false), correctionKeyword=ref(''), correctionStatus=ref(null);
|
||||
let pollTimer;
|
||||
let entriesRequesting=false,currentCastRequesting=false,restoringSelection=false;
|
||||
let entriesRequestId=0,currentCastRequesting=false,restoringSelection=false;
|
||||
let scoreScreenWindow=null;
|
||||
const scoreScreenWindowName='yd-event-score-screen';
|
||||
const scoreScreenChannelName=computed(()=>`yd-event-score-screen-${eventId.value}`);
|
||||
|
||||
const resultStatuses=[{value:0,label:'计算中'},{value:10,label:'待复核'},{value:20,label:'已确认'},{value:30,label:'已投屏'},{value:40,label:'已更正'},{value:90,label:'已作废'}];
|
||||
const sessionStatuses=[{value:0,label:'草稿'},{value:10,label:'就绪'},{value:20,label:'检录中'},{value:30,label:'进行中'},{value:40,label:'暂停'},{value:50,label:'待确认'},{value:60,label:'已结束'}];
|
||||
const sessionGroups=computed(()=>{
|
||||
const groups=new Map();
|
||||
sessions.value.forEach((session,index)=>{
|
||||
const key=String(session.eventProjectId??session.projectName??'unknown');
|
||||
if(!groups.has(key))groups.set(key,{key,projectName:session.projectName||'未命名项目',sessions:[],groupNames:new Set(),totalEntries:0,completedEntries:0,statuses:[]});
|
||||
const group=groups.get(key);
|
||||
group.sessions.push({...session,_sourceIndex:index});
|
||||
if(session.groupName)group.groupNames.add(session.groupName);
|
||||
group.totalEntries+=Number(session.totalEntries)||0;
|
||||
group.completedEntries+=Number(session.completedEntries)||0;
|
||||
group.statuses.push(Number(session.sessionStatus)||0);
|
||||
});
|
||||
return [...groups.values()].map(group=>{
|
||||
const activeStatus=[30,20,40,50].find(status=>group.statuses.includes(status));
|
||||
const sessionStatus=activeStatus??(group.statuses.every(status=>status===60)?60:group.statuses[0]??0);
|
||||
return {...group,groupCount:group.groupNames.size||1,sessionStatus,sessionStatusName:sessionStatuses.find(item=>item.value===sessionStatus)?.label||'待开始'};
|
||||
});
|
||||
});
|
||||
const selectedProject=computed(()=>sessionGroups.value.find(project=>project.key===selectedProjectId.value)||null);
|
||||
const selectedProjectSessions=computed(()=>selectedProject.value?.sessions||[]);
|
||||
const castEntityKind=ref(localStorage.getItem(`yd-event-cast-kind:${eventId.value}`)||'individual');
|
||||
const castEntityLabel=computed(()=>castEntityKind.value==='team'?'团队':'个人');
|
||||
const rankingKey=ref('');
|
||||
const rankingOptions=computed(()=>{const map=new Map();for(const x of sessions.value){if(x.eventProjectId&&x.eventProjectGroupId){const key=`${x.eventProjectId}:${x.eventProjectGroupId}`;if(!map.has(key))map.set(key,{...x,key})}}return [...map.values()]});
|
||||
const rankingSelection=computed(()=>rankingOptions.value.find(x=>x.key===rankingKey.value));
|
||||
const showDifficulty=computed(()=>!String(selectedSession.value?.scoreRuleCode||'').toUpperCase().includes('PRESCRIBED'));
|
||||
const selectedSessionDisplay=computed(()=>{
|
||||
if(!selectedSession.value)return '—';
|
||||
return formatSessionName(selectedSession.value,sessions.value.findIndex(item=>item.sessionId===selectedSession.value.sessionId)).replace(/\s+/g,'');
|
||||
});
|
||||
const showDifficulty=computed(()=>selectedProjectSessions.value.some(session=>!String(session.scoreRuleCode||'').toUpperCase().includes('PRESCRIBED')));
|
||||
const judgeMatrixGroups=computed(()=>{
|
||||
const groups=new Map();
|
||||
for(const row of entries.value){
|
||||
@ -187,8 +204,8 @@ function clearPublishedScreenCast(venueId){
|
||||
const channel=new BroadcastChannel(scoreScreenChannelName.value);channel.postMessage(null);channel.close();
|
||||
}catch{}
|
||||
}
|
||||
function buildSingleCastSnapshot(response,row){
|
||||
const result=normalizeCastValue(unwrapApiData(response)||{}),session=selectedSession.value||{};
|
||||
function buildSingleCastSnapshot(response,row,rowSession){
|
||||
const result=normalizeCastValue(unwrapApiData(response)||{}),session=rowSession||row?._session||selectedSession.value||{};
|
||||
return {...result,castType:1,displayKind:castEntityKind.value,eventName:eventName.value,projectName:session.projectName||session.eventProjectName||'',groupName:session.groupName||session.eventProjectGroupName||row.groupName||'',venueName:session.venueName||session.venueNo||'',sessionNo:session.sessionNo||'',scoreRuleCode:session.scoreRuleCode||'',payloadJson:result.payloadJson||JSON.stringify({...row,...row.result})};
|
||||
}
|
||||
function buildRankingCastSnapshot(response,target){
|
||||
@ -203,7 +220,18 @@ async function ensureEvent(){
|
||||
return false;
|
||||
}
|
||||
|
||||
async function loadSessions(){if(!await ensureEvent())return;loadingSessions.value=true;try{const res=await workbenchSessionsGet({eventId:eventId.value,keyword:sessionKeyword.value||undefined,sessionStatus:sessionStatus.value??undefined});sessions.value=res?.data||[];if(!sessions.value.some(x=>x.sessionId===selectedSession.value?.sessionId)){selectedSession.value=null;clearResultSelection()}if(!selectedSession.value&&sessions.value.length)await selectSession(sessions.value[0]);}finally{loadingSessions.value=false}}
|
||||
async function loadSessions(){
|
||||
if(!await ensureEvent())return;
|
||||
loadingSessions.value=true;
|
||||
try{
|
||||
const res=await workbenchSessionsGet({eventId:eventId.value,keyword:sessionKeyword.value||undefined,sessionStatus:sessionStatus.value??undefined});
|
||||
sessions.value=res?.data||[];
|
||||
const current=sessionGroups.value.find(project=>project.key===selectedProjectId.value);
|
||||
if(!current){selectedProjectId.value='';selectedSession.value=null;clearResultSelection()}
|
||||
if(!selectedProjectId.value&&sessionGroups.value.length)await selectProject(sessionGroups.value[0]);
|
||||
else if(current){selectedSession.value=current.sessions.find(session=>session.sessionId===selectedSession.value?.sessionId)||current.sessions[0]||null;await loadEntries()}
|
||||
}finally{loadingSessions.value=false}
|
||||
}
|
||||
function syncSelection(rows){selectedEntryIds.value=rows.map(x=>x.sessionEntryId).filter(Boolean);selectedResultIds.value=rows.map(x=>x.result?.resultId).filter(Boolean)}
|
||||
function clearResultSelection(){selectedEntryIds.value=[];selectedResultIds.value=[];workbenchTableRef.value?.clearSelection()}
|
||||
async function restoreResultSelection(previousEntryIds){
|
||||
@ -220,9 +248,27 @@ async function restoreResultSelection(previousEntryIds){
|
||||
const removedCount=selectedSet.size-validRows.length;
|
||||
if(removedCount>0)ElMessage.info(`${removedCount} 条已选成绩状态发生变化,已从批量确认中移除`);
|
||||
}
|
||||
async function selectSession(session){clearResultSelection();selectedSession.value=session;await loadEntries()}
|
||||
async function selectProject(project){clearResultSelection();selectedProjectId.value=project.key;selectedSession.value=project.sessions[0]||null;await loadEntries()}
|
||||
async function selectSession(session){
|
||||
const project=sessionGroups.value.find(item=>item.sessions.some(candidate=>candidate.sessionId===session.sessionId));
|
||||
if(!project)return;
|
||||
clearResultSelection();selectedProjectId.value=project.key;selectedSession.value=session;await loadEntries();
|
||||
}
|
||||
function selectSessionById(sessionId){const session=sessions.value.find(item=>item.sessionId===sessionId);if(session)selectSession(session)}
|
||||
async function loadEntries(silent=false){if(!selectedSession.value||entriesRequesting)return;const previousEntryIds=[...selectedEntryIds.value];entriesRequesting=true;if(!silent)loadingEntries.value=true;try{const res=await workbenchSessionEntriesGet(selectedSession.value.sessionId,{keyword:entryKeyword.value||undefined,resultStatus:resultStatus.value??undefined});entries.value=res?.data||[];offline.value=false;await restoreResultSelection(previousEntryIds)}catch{offline.value=true}finally{entriesRequesting=false;loadingEntries.value=false}}
|
||||
async function loadEntries(silent=false){
|
||||
const projectSessions=[...selectedProjectSessions.value];
|
||||
if(!projectSessions.length)return;
|
||||
const requestId=++entriesRequestId;
|
||||
const previousEntryIds=[...selectedEntryIds.value];
|
||||
if(!silent)loadingEntries.value=true;
|
||||
try{
|
||||
const responses=await Promise.all(projectSessions.map((session,sessionOrder)=>workbenchSessionEntriesGet(session.sessionId,{keyword:entryKeyword.value||undefined,resultStatus:resultStatus.value??undefined}).then(res=>(res?.data||[]).map((row,rowOrder)=>({...row,_session:session,_sessionOrder:sessionOrder,_rowOrder:rowOrder})))));
|
||||
if(requestId!==entriesRequestId)return;
|
||||
entries.value=responses.flat().sort((a,b)=>a._sessionOrder-b._sessionOrder||(Number(a.playOrder)||a._rowOrder)-(Number(b.playOrder)||b._rowOrder));
|
||||
offline.value=false;
|
||||
await restoreResultSelection(previousEntryIds);
|
||||
}catch{if(requestId===entriesRequestId)offline.value=true}finally{if(requestId===entriesRequestId)loadingEntries.value=false}
|
||||
}
|
||||
async function loadCurrentCast(){if(!eventId.value||currentCastRequesting)return;currentCastRequesting=true;try{const res=await workbenchScreenCastCurrentGet({eventId:eventId.value,venueId:selectedSession.value?.venueId});currentCast.value=res?.data||null;offline.value=false}catch{offline.value=true}finally{currentCastRequesting=false}}
|
||||
async function loadCorrections(){if(!eventId.value)return;loadingCorrections.value=true;try{const res=await workbenchCorrectionsGet({eventId:eventId.value,keyword:correctionKeyword.value||undefined,correctionStatus:correctionStatus.value??undefined});corrections.value=res?.data||[]}finally{loadingCorrections.value=false}}
|
||||
function resetFilters(){entryKeyword.value='';resultStatus.value=null;loadEntries()}
|
||||
@ -282,11 +328,12 @@ function formatSessionText(sessionNo) {
|
||||
}
|
||||
return String(sessionNo).replace(/^EP/i, '场次 ');
|
||||
}
|
||||
function rowSessionDisplay(row){const session=row?._session;if(!session)return '—';return formatSessionName(session,session._sourceIndex).replace(/\s+/g,'')}
|
||||
function onSelection(rows){if(!restoringSelection)syncSelection(rows)}
|
||||
function showDetail(row){detailRow.value=row;detailVisible.value=true}
|
||||
function handleRowCommand(command,row){if(command==='correction')openCorrection(row);else if(command==='rematch')openReason('rematch',row);else if(command==='void')openReason('void',row);else if(command==='export')exportSheets(row)}
|
||||
|
||||
async function castScore(row){if(actionLoading.value)return;const venueId=selectedSession.value.venueId;rememberCastKind();const presentation=prepareScoreScreen(venueId);rowActionId.value=row.sessionEntryId;actionLoading.value=true;try{const response=ensureCastSucceeded(await workbenchScreenCastPost({eventId:eventId.value,castType:1,venueId,sessionId:selectedSession.value.sessionId,sessionEntryId:row.sessionEntryId,eventResultId:row.result.resultId,resultVersion:row.result.resultVersion}));publishScreenCast(buildSingleCastSnapshot(response,row),venueId);showScoreScreen(presentation,venueId,castEntityKind.value);ElMessage.success(`${castEntityLabel.value}成绩已投出`);await Promise.all([loadEntries(true),loadCurrentCast()])}catch(error){closeUnusedScreen(presentation);throw error}finally{actionLoading.value=false;rowActionId.value=null}}
|
||||
async function castScore(row){if(actionLoading.value)return;const session=row._session||selectedSession.value;if(!session)return ElMessage.warning('未找到成绩所属场次');const venueId=session.venueId;rememberCastKind();const presentation=prepareScoreScreen(venueId);rowActionId.value=row.sessionEntryId;actionLoading.value=true;try{const response=ensureCastSucceeded(await workbenchScreenCastPost({eventId:eventId.value,castType:1,venueId,sessionId:session.sessionId,sessionEntryId:row.sessionEntryId,eventResultId:row.result.resultId,resultVersion:row.result.resultVersion}));publishScreenCast(buildSingleCastSnapshot(response,row,session),venueId);showScoreScreen(presentation,venueId,castEntityKind.value);ElMessage.success(`${castEntityLabel.value}成绩已投出`);await Promise.all([loadEntries(true),loadCurrentCast()])}catch(error){closeUnusedScreen(presentation);throw error}finally{actionLoading.value=false;rowActionId.value=null}}
|
||||
async function castDetailScore(){const row=detailRow.value;if(!row)return;await castScore(row);detailVisible.value=false}
|
||||
function openRanking(){rankingKey.value=selectedSession.value?.eventProjectGroupId?`${selectedSession.value.eventProjectId}:${selectedSession.value.eventProjectGroupId}`:'';rankingVisible.value=true}
|
||||
async function castRanking(){const target=rankingSelection.value;if(!target)return ElMessage.warning('请选择项目和组别');if(actionLoading.value)return;const venueId=target.venueId??selectedSession.value?.venueId;rememberCastKind();const presentation=prepareScoreScreen(venueId);actionLoading.value=true;try{const response=ensureCastSucceeded(await workbenchScreenCastPost({eventId:eventId.value,castType:2,venueId,eventProjectId:target.eventProjectId,eventProjectGroupId:target.eventProjectGroupId}));publishScreenCast(buildRankingCastSnapshot(response,target),venueId);showScoreScreen(presentation,venueId,castEntityKind.value);rankingVisible.value=false;ElMessage.success(`${castEntityLabel.value}排名已投出`);await Promise.all([loadEntries(true),loadCurrentCast()])}catch(error){closeUnusedScreen(presentation);throw error}finally{actionLoading.value=false}}
|
||||
@ -304,8 +351,10 @@ async function batchConfirm(){
|
||||
if(validRows.length!==entryIdsSnapshot.length){await restoreResultSelection(entryIdsSnapshot);ElMessage.warning('部分成绩状态已变化,请检查当前选择后重新确认');return}
|
||||
const resultIdsSnapshot=validRows.map(row=>row.result?.resultId).filter(Boolean);
|
||||
if(resultIdsSnapshot.length!==validRows.length){ElMessage.warning('部分成绩缺少有效结果,请刷新后重试');return}
|
||||
const requestsBySession=new Map();
|
||||
for(const row of validRows){const sessionId=row._session?.sessionId;if(!sessionId){ElMessage.warning('部分成绩缺少场次信息,请刷新后重试');return}if(!requestsBySession.has(sessionId))requestsBySession.set(sessionId,[]);requestsBySession.get(sessionId).push(row.result.resultId)}
|
||||
actionLoading.value=true;
|
||||
try{await workbenchResultsBatchConfirmPost({sessionId:selectedSession.value.sessionId,resultIds:resultIdsSnapshot});clearResultSelection();ElMessage.success('批量确认完成');await loadEntries()}finally{actionLoading.value=false}
|
||||
try{await Promise.all([...requestsBySession].map(([sessionId,resultIds])=>workbenchResultsBatchConfirmPost({sessionId,resultIds})));clearResultSelection();ElMessage.success('批量确认完成');await loadEntries()}finally{actionLoading.value=false}
|
||||
}finally{batchConfirmDialogOpen.value=false}
|
||||
}
|
||||
function openReason(action,row){reasonAction.value=action;reasonRow.value=row;reasonText.value='';reasonVisible.value=true}
|
||||
@ -316,8 +365,8 @@ async function approveCorrection(row,isApproved){let remark='';if(!isApproved)re
|
||||
async function showCorrectionDetail(row){const res=await workbenchCorrectionDetailGet(row.correctionId);const x=res?.data;if(!x)return;ElMessageBox.alert(`选手:${x.entry?.participantName||'-'}\n裁判:${x.originalScore?.judgeName||'-'}(${x.originalScore?.judgeRoleName||'-'})\n原分:${score(x.originalScore?.totalScore)}\n原因:${x.applyReason}`,'修正详情',{confirmButtonText:'关闭'})}
|
||||
|
||||
async function download(blob,name){const url=URL.createObjectURL(blob);const a=document.createElement('a');a.href=url;a.download=name;document.body.appendChild(a);a.click();a.remove();URL.revokeObjectURL(url)}
|
||||
async function exportResults(){const blob=await workbenchResultsExportGet({eventId:eventId.value,sessionId:selectedSession.value?.sessionId,keyword:entryKeyword.value||undefined,resultStatus:resultStatus.value??undefined});download(blob,`成绩册_${eventId.value}.xlsx`)}
|
||||
async function exportSheets(){const blob=await workbenchScoreSheetsExportGet({eventId:eventId.value,sessionId:selectedSession.value.sessionId});download(blob,`评分单_${selectedSession.value.projectName || ''}_${formatSessionName(selectedSession.value)}.xlsx`)}
|
||||
async function exportResults(){const blob=await workbenchResultsExportGet({eventId:eventId.value,eventProjectId:selectedSession.value?.eventProjectId,keyword:entryKeyword.value||undefined,resultStatus:resultStatus.value??undefined});download(blob,`成绩册_${selectedProject.value?.projectName||eventId.value}.xlsx`)}
|
||||
async function exportSheets(row){const session=row?._session||selectedSession.value;if(!session)return;const blob=await workbenchScoreSheetsExportGet({eventId:eventId.value,sessionId:session.sessionId});download(blob,`评分单_${session.projectName || ''}_${formatSessionName(session,session._sourceIndex)}.xlsx`)}
|
||||
const presentationFrameId='yd-event-presentation-frame';
|
||||
function scoreScreenHref(venueId,displayKind=castEntityKind.value,castMode='live'){return router.resolve({path:'/scoring/screen',query:{eventId:eventId.value,venueId,eventName:eventName.value,mode:'freestyle',castMode,displayKind,autofullscreen:'1',presentation:'1'}}).href}
|
||||
function ensurePresentationShell(popup){
|
||||
@ -589,37 +638,37 @@ onBeforeUnmount(()=>window.clearInterval(pollTimer));
|
||||
.session-head { margin-bottom: 14px; }
|
||||
.session-head b { color: var(--wb-text); font-size: 15px; }
|
||||
.session-list { margin-top: 14px; padding-right: 3px; }
|
||||
.session-list > button {
|
||||
position: relative;
|
||||
.session-list > .project-switch {
|
||||
grid-template-columns: 34px minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 8px 10px;
|
||||
margin-bottom: 10px;
|
||||
padding: 14px;
|
||||
border-color: #e7edf5;
|
||||
border-radius: 11px;
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
|
||||
padding: 13px 12px;
|
||||
border-color: #e3eaf3;
|
||||
border-radius: 12px;
|
||||
transition: border-color .2s ease,box-shadow .2s ease,transform .2s ease;
|
||||
}
|
||||
.session-list > button::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -1px;
|
||||
top: 12px;
|
||||
bottom: 12px;
|
||||
width: 3px;
|
||||
border-radius: 0 3px 3px 0;
|
||||
background: var(--wb-primary);
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
.session-list > .project-switch:hover { transform: translateY(-1px); }
|
||||
.session-list > .project-switch.active { border-color:#93c5fd;background:linear-gradient(135deg,#f2f8ff,#f8fcff);box-shadow:0 8px 20px rgba(22,119,255,.1); }
|
||||
.project-sequence {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
grid-row: 1 / 3;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 9px;
|
||||
color: #1677ff;
|
||||
background: #eaf3ff;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.session-list > button:hover { transform: translateY(-1px); }
|
||||
.session-list > button.active {
|
||||
border-color: #93c5fd;
|
||||
background: linear-gradient(135deg, #f2f8ff, #f8fcff);
|
||||
box-shadow: 0 8px 20px rgba(22, 119, 255, 0.1);
|
||||
}
|
||||
.session-list > button.active::before { opacity: 1; }
|
||||
.session-list strong { color: #223652; font-size: 14px; }
|
||||
.session-list div span { line-height: 1.55; }
|
||||
.session-list em { border: 1px solid #d8ecff; background: #f0f8ff; }
|
||||
.session-list i { height: 5px; }
|
||||
.project-switch > div { min-width:0; }
|
||||
.project-switch > div strong { overflow:hidden;color:#223652;font-size:13px;text-overflow:ellipsis;white-space:nowrap; }
|
||||
.project-switch > div span { margin-top:4px;color:#8fa0b2;font-size:10px; }
|
||||
.project-switch > em { align-self:start;border:1px solid #d8ecff;background:#f0f8ff; }
|
||||
.project-switch > i { grid-column:2;height:5px; }
|
||||
.project-switch > small { grid-column:3;grid-row:2;color:#7f91a4;text-align:right; }
|
||||
.group-cell { display:inline-flex;padding:4px 8px;border-radius:6px;color:#315f92;background:#edf5ff;font-size:12px; }
|
||||
|
||||
.result-panel { display: flex; flex-direction: column; padding: 0; }
|
||||
.screen-control-panel {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user