update:样式修改&接口对接
This commit is contained in:
parent
c799752f63
commit
74c73c6ba2
BIN
src/assets/scoring/screen-wave.png
Normal file
BIN
src/assets/scoring/screen-wave.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.1 MiB |
@ -244,7 +244,7 @@ const columns = reactive([
|
||||
prop: 'matchTime',
|
||||
label: '比赛时间',
|
||||
},
|
||||
{ prop: 'action', label: '操作', align: 'center', width: 200, fixed: 'right', isShow: auths(['event:edit', 'event:delete', 'event:workbench']) },
|
||||
{ prop: 'action', label: '操作', align: 'center', width: 250, fixed: 'right', isShow: auths(['event:edit', 'event:delete', 'event:workbench']) },
|
||||
]);
|
||||
|
||||
const exportableColumns = computed(() => [
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -23,25 +23,25 @@
|
||||
</aside>
|
||||
|
||||
<main class="result-panel">
|
||||
<div v-if="selectedSession" class="result-heading">
|
||||
<div><b>{{ selectedSession.projectName || '未命名项目' }}</b><span>{{ selectedSession.groupName || '混合组别' }}</span><small>场次 {{ selectedSession.sessionNo }} · {{ selectedSession.venueName }}</small></div>
|
||||
<div class="entry-count"><b>{{ entries.length }}</b><small>当前列表</small></div>
|
||||
</div>
|
||||
<div class="filterbar">
|
||||
<el-input v-model="entryKeyword" clearable placeholder="姓名、队伍或出场编号" @keyup.enter="loadEntries" />
|
||||
<el-select v-model="resultStatus" clearable placeholder="成绩状态" @change="loadEntries"><el-option v-for="x in resultStatuses" :key="x.value" :label="x.label" :value="x.value" /></el-select>
|
||||
<el-button type="primary" @click="loadEntries">查询</el-button>
|
||||
<el-button @click="resetFilters">重置</el-button>
|
||||
<div class="filter-fields"><el-input v-model="entryKeyword" clearable placeholder="搜索姓名、队伍或出场编号" @keyup.enter="loadEntries" /><el-select v-model="resultStatus" clearable placeholder="全部成绩状态" @change="loadEntries"><el-option v-for="x in resultStatuses" :key="x.value" :label="x.label" :value="x.value" /></el-select><el-button type="primary" @click="loadEntries">查询</el-button><el-button text @click="resetFilters">重置</el-button></div>
|
||||
<span class="filter-spacer"></span>
|
||||
<el-button v-if="activeTab === 'screen' || activeTab === 'results'" type="success" :disabled="!rankingOptions.length" @click="openRanking">投出排名</el-button>
|
||||
<el-button v-if="activeTab === 'screen'" @click="openScoreScreen">打开成绩大屏</el-button>
|
||||
<div class="toolbar-actions"><el-button v-if="activeTab === 'screen' || activeTab === 'results'" type="success" :disabled="!rankingOptions.length" @click="openRanking">投出排名</el-button><el-button v-if="activeTab === 'screen'" @click="openScoreScreen">打开成绩大屏</el-button>
|
||||
<template v-if="activeTab === 'results'">
|
||||
<el-button type="primary" :loading="actionLoading" :disabled="!selectedResultIds.length" @click="batchConfirm">批量确认</el-button>
|
||||
<el-button @click="exportResults">成绩导出</el-button>
|
||||
</template>
|
||||
</template></div>
|
||||
</div>
|
||||
|
||||
<div v-if="activeTab === 'screen' && currentCast" class="cast-banner">
|
||||
<span>LIVE</span><div><b>当前正在投屏:{{ currentCast.castType === 1 ? '单人成绩' : '组别排名' }}</b><small>{{ formatTime(currentCast.castTime) }}</small></div><el-button text type="danger" @click="withdrawCast">撤回投屏</el-button>
|
||||
<div v-if="currentCast" class="cast-banner">
|
||||
<span>LIVE</span><div><b>当前正在展示:{{ currentCastSummary }}</b><small>{{ formatTime(currentCast.castTime) }}</small></div><el-button text type="primary" @click="openScoreScreen">查看大屏</el-button><el-button text type="danger" @click="withdrawCast">撤回投屏</el-button>
|
||||
</div>
|
||||
|
||||
<el-table v-loading="loadingEntries" :data="entries" row-key="sessionEntryId" height="calc(100vh - 245px)" @selection-change="onSelection">
|
||||
<div class="table-wrap"><el-table v-loading="loadingEntries" :data="entries" row-key="sessionEntryId" height="100%" class="workbench-table" highlight-current-row @selection-change="onSelection">
|
||||
<el-table-column v-if="activeTab === 'results'" type="selection" width="44" :selectable="canConfirm" />
|
||||
<el-table-column prop="playOrder" label="序号" width="64" align="center"><template #default="{ row }"><b class="order">{{ row.playOrder }}</b><i v-if="row.isRematch" class="rematch-tag">重赛</i></template></el-table-column>
|
||||
<el-table-column label="选手 / 队伍" min-width="190"><template #default="{ row }"><strong>{{ row.teamName || row.participantName }}</strong><small class="subtext">{{ row.entryNo }} · {{ row.groupName }}<template v-if="row.unitName"> · {{row.unitName}}</template></small></template></el-table-column>
|
||||
@ -52,11 +52,11 @@
|
||||
<el-table-column label="扣分" width="88" align="center"><template #default="{ row }">{{ score(row.result?.deductionScore) }}</template></el-table-column>
|
||||
<el-table-column label="总分" width="110" 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="100"><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' ? 150 : 330" fixed="right"><template #default="{ row }">
|
||||
<template v-if="activeTab === 'screen'"><el-tooltip :content="castDisabledReason(row)" :disabled="canCast(row)"><span><el-button text type="primary" :disabled="!canCast(row)" @click="castScore(row)">投出成绩</el-button></span></el-tooltip><el-button text @click="showDetail(row)">详情</el-button></template>
|
||||
<template v-else><el-button text type="primary" @click="showDetail(row)">详情</el-button><el-button v-if="canConfirm(row)" text type="success" @click="confirmOne(row)">确认</el-button><el-button text type="warning" :disabled="!row.judgeScores?.length" @click="openCorrection(row)">成绩修正</el-button><el-button text type="danger" @click="confirmRematch(row)">重赛</el-button><el-dropdown trigger="click"><el-button text>更多</el-button><template #dropdown><el-dropdown-menu><el-dropdown-item @click="openReason('void', row)">作废成绩</el-dropdown-item><el-dropdown-item @click="exportSheets(row)">导出评分单</el-dropdown-item></el-dropdown-menu></template></el-dropdown></template>
|
||||
<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="canCast(row)"><span><el-button size="small" type="primary" :loading="actionLoading && rowActionId === row.sessionEntryId" :disabled="!canCast(row)" @click="castScore(row)">展示成绩</el-button></span></el-tooltip><el-button size="small" @click="showDetail(row)">评分详情</el-button></div>
|
||||
<div v-else class="row-actions"><el-button v-if="canConfirm(row)" size="small" type="success" :loading="actionLoading && rowActionId === row.sessionEntryId" @click="confirmOne(row)">确认成绩</el-button><el-button v-else-if="canCast(row)" size="small" type="primary" :loading="actionLoading && rowActionId === row.sessionEntryId" @click="castScore(row)">展示成绩</el-button><el-button v-else size="small" disabled>等待成绩</el-button><el-button size="small" @click="showDetail(row)">详情</el-button><el-dropdown trigger="click" @command="command => handleRowCommand(command, row)"><el-button size="small">更多 <span class="dropdown-arrow">⌄</span></el-button><template #dropdown><el-dropdown-menu><el-dropdown-item command="correction" :disabled="!row.judgeScores?.length">发起成绩修正</el-dropdown-item><el-dropdown-item command="rematch" divided>安排重赛</el-dropdown-item><el-dropdown-item command="export">导出本场评分单</el-dropdown-item><el-dropdown-item command="void" divided class="danger-menu-item">作废成绩</el-dropdown-item></el-dropdown-menu></template></el-dropdown></div>
|
||||
</template></el-table-column>
|
||||
</el-table>
|
||||
</el-table></div>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
@ -67,14 +67,14 @@
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<el-dialog v-model="detailVisible" title="评分详情" width="92%" destroy-on-close><div v-if="detailRow" class="detail-summary"><div><small>选手/队伍</small><b>{{detailRow.teamName || detailRow.participantName}}</b></div><div><small>总分</small><b>{{score(detailRow.result?.totalScore)}}</b></div><div><small>成绩版本</small><b>V{{detailRow.result?.resultVersion || 0}}</b></div></div><el-table :data="detailRows" max-height="520"><el-table-column prop="judgeRole" label="席位" width="80" fixed/><el-table-column prop="judgeRoleName" label="裁判类型" min-width="110" fixed/><el-table-column prop="judgeName" label="裁判" width="100" fixed/><el-table-column v-for="col in detailColumns" :key="col.key" :label="col.label" min-width="105" align="center"><template #default="{row}">{{score(row.itemValues[col.key])}}</template></el-table-column><el-table-column prop="totalScore" label="合计" width="90"/><el-table-column prop="sheetStatusName" label="状态" width="95"/></el-table></el-dialog>
|
||||
<el-dialog v-model="detailVisible" title="评分详情" width="92%" destroy-on-close><div v-if="detailRow" class="detail-summary"><div><small>选手/队伍</small><b>{{detailRow.teamName || detailRow.participantName}}</b></div><div><small>总分</small><b>{{score(detailRow.result?.totalScore)}}</b></div><div><small>成绩版本</small><b>V{{detailRow.result?.resultVersion || 0}}</b></div></div><el-table :data="detailRows" max-height="520"><el-table-column prop="judgeRole" label="席位" width="80" fixed/><el-table-column prop="judgeRoleName" label="裁判类型" min-width="110" fixed/><el-table-column prop="judgeName" label="裁判" width="100" fixed/><el-table-column v-for="col in detailColumns" :key="col.key" :label="col.label" min-width="105" align="center"><template #default="{row}">{{score(row.itemValues[col.key])}}</template></el-table-column><el-table-column prop="totalScore" label="合计" width="90"/><el-table-column prop="sheetStatusName" label="状态" width="95"/></el-table><template #footer><el-button @click="detailVisible=false">关闭</el-button><el-button type="primary" :loading="actionLoading" :disabled="!canCast(detailRow)" @click="castDetailScore">展示该成绩</el-button></template></el-dialog>
|
||||
|
||||
<el-dialog v-model="rankingVisible" title="投出组别排名" width="520px"><el-alert type="info" :closable="false" title="请选择需要投屏的项目和组别"/><el-select v-model="rankingKey" placeholder="选择项目 / 组别" style="width:100%;margin-top:16px"><el-option v-for="x in rankingOptions" :key="x.key" :label="`${x.projectName} / ${x.groupName}`" :value="x.key"/></el-select><template #footer><el-button @click="rankingVisible=false">取消</el-button><el-button type="primary" :loading="actionLoading" :disabled="!rankingKey" @click="castRanking">确定投屏</el-button></template></el-dialog>
|
||||
|
||||
<el-dialog v-model="reasonVisible" title="作废成绩" width="520px"><el-form label-position="top" class="reason-form"><el-form-item label="作废原因" required><el-input v-model="reasonText" type="textarea" :rows="4" maxlength="200" show-word-limit placeholder="请填写 2-200 个字符"/></el-form-item></el-form><template #footer><el-button @click="reasonVisible=false">取消</el-button><el-button type="danger" :loading="actionLoading" @click="submitReason">确认</el-button></template></el-dialog>
|
||||
|
||||
<el-dialog v-model="correctionVisible" title="发起成绩修正" width="560px"><el-form label-position="top"><el-form-item label="裁判类型" required><el-select v-model="correctionForm.judgeRoleType" placeholder="请选择裁判类型" style="width:100%" @change="onCorrectionRoleChange"><el-option v-for="group in correctionGroups" :key="group.judgeRoleType" :label="group.judgeRoleName" :value="group.judgeRoleType"/></el-select></el-form-item><el-form-item label="评分单" required><el-select v-model="correctionForm.scoreSheetId" :disabled="!correctionForm.judgeRoleType" placeholder="请选择已提交评分单" style="width:100%"><el-option v-for="x in correctionSheetOptions" :key="x.scoreSheetId" :disabled="!isCorrectionSheetAvailable(x)" :label="`${x.judgeRole}号位 · ${x.judgeName} · ${score(x.totalScore)}分 · ${x.sheetStatusName}${x.hasPendingCorrection?'(处理中)':''}`" :value="x.scoreSheetId"/></el-select></el-form-item><el-form-item label="修正原因" required><el-input v-model="correctionForm.reason" type="textarea" :rows="3" maxlength="200" show-word-limit placeholder="请填写 2-200 个字符"/></el-form-item></el-form><template #footer><el-button @click="correctionVisible=false">取消</el-button><el-button type="primary" :loading="actionLoading" @click="submitCorrection">发送修正任务</el-button></template></el-dialog>
|
||||
<el-dialog v-model="rematchVisible" title="这是进行一项操作时必须了解的重要信息" width="520px"><div class="dialog-desc">确认重赛,本组选手将顺延至当前组别最后出场进行重赛,你还要继续吗?</div><template #footer><el-button @click="rematchVisible=false">取消</el-button><el-button type="danger" :loading="actionLoading" @click="submitRematch">继续</el-button></template></el-dialog>
|
||||
<el-dialog v-model="rematchVisible" title="确认安排重赛" width="520px"><div class="dialog-desc">确认后,该选手将顺延至当前组别末尾重新出场。请确认现场安排已就绪。</div><template #footer><el-button @click="rematchVisible=false">取消</el-button><el-button type="danger" :loading="actionLoading" @click="submitRematch">确认安排</el-button></template></el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -97,11 +97,14 @@ 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 loadingSessions=ref(false), loadingEntries=ref(false), offline=ref(false), sessionKeyword=ref(''), sessionStatus=ref(null), entryKeyword=ref(''), resultStatus=ref(null), selectedResultIds=ref([]);
|
||||
const detailVisible=ref(false), detailRow=ref(null), rankingVisible=ref(false), reasonVisible=ref(false), reasonAction=ref(''), reasonText=ref(''), reasonRow=ref(null), actionLoading=ref(false);
|
||||
const correctionVisible=ref(false), correctionRow=ref(null), correctionForm=ref({scoreSheetId:null,reason:''});
|
||||
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:'',scoreSheetId:null,reason:''});
|
||||
const rematchVisible=ref(false), rematchRow=ref(null);
|
||||
const corrections=ref([]), loadingCorrections=ref(false), correctionKeyword=ref(''), correctionStatus=ref(null);
|
||||
let pollTimer;
|
||||
let entriesRequesting=false,currentCastRequesting=false;
|
||||
let scoreScreenWindow=null;
|
||||
const scoreScreenWindowName='yd-event-score-screen';
|
||||
|
||||
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:'已结束'}];
|
||||
@ -111,9 +114,21 @@ const rankingSelection=computed(()=>rankingOptions.value.find(x=>x.key===ranking
|
||||
const showDifficulty=computed(()=>!String(selectedSession.value?.scoreRuleCode||'').toUpperCase().includes('PRESCRIBED'));
|
||||
const detailColumns=computed(()=>{const map=new Map();for(const sheet of detailRow.value?.judgeScores||[])for(const item of sheet.scoreItems||[]){const key=`${item.itemCode}:${item.subjectCode||''}`;if(!map.has(key))map.set(key,{key,label:item.subjectCode?`${item.itemName}-${item.subjectCode}`:item.itemName,sort:item.sort})}return [...map.values()].sort((a,b)=>a.sort-b.sort)});
|
||||
const detailRows=computed(()=>(detailRow.value?.judgeScores||[]).map(sheet=>({...sheet,itemValues:Object.fromEntries((sheet.scoreItems||[]).map(item=>[`${item.itemCode}:${item.subjectCode||''}`,item.inputValue??item.countValue??item.subtotal]))})));
|
||||
const correctionGroups=computed(()=>{const groups=new Map();for(const sheet of correctionRow.value?.judgeScores||[]){const key=sheet.judgeRoleType||sheet.judgeRoleName;if(key&&!groups.has(key))groups.set(key,{judgeRoleType:key,judgeRoleName:sheet.judgeRoleName||key})}return [...groups.values()]});
|
||||
const correctionSheetOptions=computed(()=>(correctionRow.value?.judgeScores||[]).filter(sheet=>(sheet.judgeRoleType||sheet.judgeRoleName)===correctionForm.value.judgeRoleType));
|
||||
const correctionStatuses=[{value:0,label:'已申请'},{value:10,label:'已批准'},{value:20,label:'已拒绝'},{value:30,label:'裁判修改中'},{value:40,label:'已完成'}];
|
||||
const tabTitle=computed(()=>({screen:'成绩投屏',results:'成绩管理',corrections:'成绩修正申请'})[activeTab.value]);
|
||||
const tabDescription=computed(()=>({screen:'选择已完成成绩投到现场大屏',results:'复核成绩、查看评分单并处理异常比赛',corrections:'审批裁判发起的修正申请并追踪处理进度'})[activeTab.value]);
|
||||
const currentCastPayload=computed(()=>{try{const value=JSON.parse(currentCast.value?.payloadJson||'{}');return Array.isArray(value)?value.map(normalizeCastValue):normalizeCastValue(value)}catch{return {}}});
|
||||
const currentCastSummary=computed(()=>{
|
||||
if(!currentCast.value)return '—';
|
||||
if(currentCast.value.castType===2)return `${currentCast.value.projectName||'当前项目'} / ${currentCast.value.groupName||'当前组别'}排名`;
|
||||
const data=currentCastPayload.value;
|
||||
const name=data.teamName||data.participantName||'未命名选手';
|
||||
return `${name} · ${score(data.totalScore??data.score)}分`;
|
||||
});
|
||||
|
||||
function normalizeCastValue(value){if(!value||typeof value!=='object')return value;return Object.fromEntries(Object.entries(value).map(([key,item])=>[key.charAt(0).toLowerCase()+key.slice(1),item]))}
|
||||
|
||||
async function ensureEvent(){
|
||||
if(eventId.value)return true;
|
||||
@ -123,7 +138,7 @@ async function ensureEvent(){
|
||||
}
|
||||
|
||||
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;if(!selectedSession.value&&sessions.value.length)await selectSession(sessions.value[0]);}finally{loadingSessions.value=false}}
|
||||
async function selectSession(session){selectedSession.value=session;await loadEntries()}
|
||||
async function selectSession(session){selectedResultIds.value=[];selectedSession.value=session;await loadEntries()}
|
||||
async function loadEntries(silent=false){if(!selectedSession.value||entriesRequesting)return;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}catch{offline.value=true}finally{entriesRequesting=false;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}}
|
||||
@ -139,24 +154,59 @@ function resultTag(status){return ({10:'warning',20:'success',30:'primary',40:'s
|
||||
function formatTime(value){return value?String(value).replace('T',' ').slice(0,19):'—'}
|
||||
function onSelection(rows){selectedResultIds.value=rows.map(x=>x.result?.resultId).filter(Boolean)}
|
||||
function showDetail(row){detailRow.value=row;detailVisible.value=true}
|
||||
function handleRowCommand(command,row){if(command==='correction')openCorrection(row);else if(command==='rematch')confirmRematch(row);else if(command==='void')openReason('void',row);else if(command==='export')exportSheets(row)}
|
||||
|
||||
async function castScore(row){if(actionLoading.value)return;actionLoading.value=true;try{await workbenchScreenCastPost({eventId:eventId.value,castType:1,venueId:selectedSession.value.venueId,sessionId:selectedSession.value.sessionId,sessionEntryId:row.sessionEntryId,eventResultId:row.result.resultId,resultVersion:row.result.resultVersion});ElMessage.success('成绩已投出');await Promise.all([loadEntries(true),loadCurrentCast()])}finally{actionLoading.value=false}}
|
||||
async function castScore(row){if(actionLoading.value)return;const presentation=prepareScoreScreen(selectedSession.value.venueId);rowActionId.value=row.sessionEntryId;actionLoading.value=true;try{await workbenchScreenCastPost({eventId:eventId.value,castType:1,venueId:selectedSession.value.venueId,sessionId:selectedSession.value.sessionId,sessionEntryId:row.sessionEntryId,eventResultId:row.result.resultId,resultVersion:row.result.resultVersion});showScoreScreen(presentation,selectedSession.value.venueId);ElMessage.success('成绩已投出');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;actionLoading.value=true;try{await workbenchScreenCastPost({eventId:eventId.value,castType:2,venueId:selectedSession.value?.venueId,eventProjectId:target.eventProjectId,eventProjectGroupId:target.eventProjectGroupId});rankingVisible.value=false;ElMessage.success('组别排名已投出');await Promise.all([loadEntries(true),loadCurrentCast()])}finally{actionLoading.value=false}}
|
||||
async function castRanking(){const target=rankingSelection.value;if(!target)return ElMessage.warning('请选择项目和组别');if(actionLoading.value)return;const venueId=target.venueId??selectedSession.value?.venueId;const presentation=prepareScoreScreen(venueId);actionLoading.value=true;try{await workbenchScreenCastPost({eventId:eventId.value,castType:2,venueId,eventProjectId:target.eventProjectId,eventProjectGroupId:target.eventProjectGroupId});showScoreScreen(presentation,venueId);rankingVisible.value=false;ElMessage.success('组别排名已投出');await Promise.all([loadEntries(true),loadCurrentCast()])}catch(error){closeUnusedScreen(presentation);throw error}finally{actionLoading.value=false}}
|
||||
async function withdrawCast(){if(actionLoading.value)return;await ElMessageBox.confirm('撤回后大屏将回到等待状态,是否继续?','撤回投屏',{type:'warning'});actionLoading.value=true;try{await workbenchScreenCastWithdrawPost(currentCast.value.screenCastId);currentCast.value=null;ElMessage.success('已撤回')}finally{actionLoading.value=false}}
|
||||
async function confirmOne(row){if(actionLoading.value)return;actionLoading.value=true;try{await workbenchResultConfirmPost(row.result.resultId);ElMessage.success('成绩已确认');await loadEntries()}finally{actionLoading.value=false}}
|
||||
async function confirmOne(row){if(actionLoading.value)return;rowActionId.value=row.sessionEntryId;actionLoading.value=true;try{await workbenchResultConfirmPost(row.result.resultId);ElMessage.success('成绩已确认');await loadEntries()}finally{actionLoading.value=false;rowActionId.value=null}}
|
||||
async function batchConfirm(){if(actionLoading.value)return;await ElMessageBox.confirm(`确认选中的 ${selectedResultIds.value.length} 条成绩?`,'批量确认');actionLoading.value=true;try{await workbenchResultsBatchConfirmPost({sessionId:selectedSession.value.sessionId,resultIds:selectedResultIds.value});ElMessage.success('批量确认完成');await loadEntries()}finally{actionLoading.value=false}}
|
||||
function openReason(action,row){reasonAction.value=action;reasonRow.value=row;reasonText.value='';reasonVisible.value=true}
|
||||
async function submitReason(){const text=reasonText.value.trim();if(text.length<2)return ElMessage.warning('请填写至少 2 个字符的原因');actionLoading.value=true;try{if(reasonAction.value==='rematch')await workbenchEntryRematchPost({originalSessionEntryId:reasonRow.value.sessionEntryId,rematchReason:text});else await workbenchResultVoidPost({sessionEntryId:reasonRow.value.sessionEntryId,voidReason:text});reasonVisible.value=false;ElMessage.success(reasonAction.value==='rematch'?'重赛已安排到场次末尾':'成绩已作废');loadEntries()}finally{actionLoading.value=false}}
|
||||
function openCorrection(row){correctionRow.value=row;correctionForm.value={scoreSheetId:null,reason:''};correctionVisible.value=true}
|
||||
function openCorrection(row){correctionRow.value=row;correctionForm.value={judgeRoleType:'',scoreSheetId:null,reason:''};correctionVisible.value=true}
|
||||
function onCorrectionRoleChange(){correctionForm.value.scoreSheetId=null}
|
||||
function isCorrectionSheetAvailable(sheet){return sheet.sheetStatus>=10&&sheet.sheetStatus<90&&!sheet.hasPendingCorrection}
|
||||
async function submitCorrection(){const reason=correctionForm.value.reason.trim();if(!correctionForm.value.scoreSheetId)return ElMessage.warning('请选择裁判评分单');if(reason.length<2)return ElMessage.warning('请填写至少 2 个字符的修正原因');actionLoading.value=true;try{await judgeScoreCorrectionInitiatePost({scoreSheetId:correctionForm.value.scoreSheetId,reason});correctionVisible.value=false;ElMessage.success('修正任务已发送给裁判');loadEntries()}finally{actionLoading.value=false}}
|
||||
function confirmRematch(row){rematchRow.value=row;rematchVisible.value=true}
|
||||
async function submitRematch(){if(!rematchRow.value||actionLoading.value)return;actionLoading.value=true;try{await workbenchEntryRematchPost({originalSessionEntryId:rematchRow.value.sessionEntryId,rematchReason:'后台确认安排重赛'});rematchVisible.value=false;ElMessage.success('重赛已安排到当前组别末尾');await loadEntries()}finally{actionLoading.value=false}}
|
||||
async function approveCorrection(row,isApproved){let remark='';if(!isApproved)remark=await ElMessageBox.prompt('请输入驳回原因','驳回修正申请',{inputValidator:v=>String(v||'').trim().length>=2||'至少输入2个字符'}).then(x=>x.value);else await ElMessageBox.confirm('批准后将向裁判下发修正任务,是否继续?','批准申请');await workbenchCorrectionApprovePost({correctionId:row.correctionId,isApproved,approveRemark:remark});ElMessage.success('申请已处理');loadCorrections()}
|
||||
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.sessionNo}.xlsx`)}
|
||||
function openScoreScreen(){window.open(router.resolve({path:'/scoring/screen',query:{eventId:eventId.value,venueId:selectedSession.value?.venueId,eventName:eventName.value,mode:'freestyle'}}).href,'_blank')}
|
||||
function scoreScreenHref(venueId){return router.resolve({path:'/scoring/screen',query:{eventId:eventId.value,venueId,eventName:eventName.value,mode:'freestyle',autofullscreen:'1'}}).href}
|
||||
function prepareScoreScreen(venueId){
|
||||
const existing=scoreScreenWindow&&!scoreScreenWindow.closed?scoreScreenWindow:null;
|
||||
const popup=window.open('',scoreScreenWindowName,'popup=yes,width=1280,height=720,left=0,top=0');
|
||||
if(!popup){ElMessage.warning('浏览器已拦截投屏窗口,请允许本站弹出窗口');return null}
|
||||
scoreScreenWindow=popup;
|
||||
const presentation={popup,created:!existing};
|
||||
if(presentation.created){try{popup.document.title='正在准备成绩大屏';popup.document.body.innerHTML='<div style="height:100vh;display:grid;place-items:center;margin:0;background:#020a3c;color:#8feeff;font:28px Microsoft YaHei">正在准备投屏…</div>'}catch{}}
|
||||
placeOnPresentationScreen(popup);
|
||||
return presentation;
|
||||
}
|
||||
async function placeOnPresentationScreen(popup){
|
||||
try{
|
||||
let target=window.screen;
|
||||
if('getScreenDetails' in window){
|
||||
const details=await window.getScreenDetails();
|
||||
target=details.screens.find(item=>item.left!==details.currentScreen.left||item.top!==details.currentScreen.top)||details.currentScreen||target;
|
||||
}
|
||||
popup.moveTo(target.availLeft??target.left??0,target.availTop??target.top??0);
|
||||
popup.resizeTo(target.availWidth??target.width,target.availHeight??target.height);
|
||||
popup.focus();
|
||||
}catch{try{popup.focus()}catch{}}
|
||||
}
|
||||
function showScoreScreen(presentation,venueId){
|
||||
const popup=presentation?.popup&&!presentation.popup.closed?presentation.popup:window.open(scoreScreenHref(venueId),scoreScreenWindowName,'popup=yes,width=1280,height=720');
|
||||
if(!popup){ElMessage.warning('成绩已投出,但浏览器拦截了大屏窗口');return}
|
||||
scoreScreenWindow=popup;popup.location.replace(scoreScreenHref(venueId));placeOnPresentationScreen(popup);
|
||||
}
|
||||
function closeUnusedScreen(presentation){if(presentation?.created&&presentation.popup&&!presentation.popup.closed)presentation.popup.close()}
|
||||
function openScoreScreen(){const presentation=prepareScoreScreen(selectedSession.value?.venueId);if(presentation)showScoreScreen(presentation,selectedSession.value?.venueId)}
|
||||
function openCheckinScreen(){window.open(router.resolve({path:'/scoring/checkin-screen',query:{eventId:eventId.value}}).href,'_blank')}
|
||||
function goLauncher(){router.push({path:'/scoring/launcher'})}
|
||||
function toggleCorrections(){activeTab.value=activeTab.value==='corrections'?'results':'corrections';if(activeTab.value==='corrections')loadCorrections()}
|
||||
@ -177,3 +227,195 @@ onBeforeUnmount(()=>window.clearInterval(pollTimer));
|
||||
.workbench-page{height:calc(100vh - 115px);display:flex;overflow:hidden;color:#172b4d;background:#f2f6fb;font-family:"Microsoft YaHei",sans-serif}.workbench-main{min-width:0;flex:1;display:flex;flex-direction:column}.topbar{height:80px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 28px;border-bottom:1px solid #e2eaf3;background:#fff}.topbar h1{margin:0;font-size:22px}.topbar p{margin:5px 0 0;color:#91a0b4;font-size:12px}.top-actions{display:flex;align-items:center;gap:10px}.live-dot{color:#22b573;font-size:12px}.live-dot.offline{color:#e74c3c}.workspace{min-height:0;flex:1;display:flex;padding:16px;gap:14px}.session-panel{width:278px;flex:none;display:flex;flex-direction:column;padding:16px;border:1px solid #e1eaf3;border-radius:12px;background:#fff}.session-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}.session-list{min-height:0;overflow:auto;margin-top:12px}.session-list>button{position:relative;display:grid;grid-template-columns:1fr auto;gap:8px;width:100%;margin-bottom:9px;padding:13px;border:1px solid #e4ecf4;border-radius:10px;color:#29435f;background:#fff;text-align:left;cursor:pointer}.session-list>button:hover,.session-list>button.active{border-color:#1999f5;background:#f0f9ff;box-shadow:0 6px 16px rgba(28,136,221,.1)}.session-list strong,.session-list span{display:block}.session-list strong{font-size:13px}.session-list div span{margin-top:7px;color:#8fa0b2;font-size:11px}.session-list em{padding:3px 6px;height:max-content;border-radius:10px;color:#178bdf;background:#eaf6ff;font-size:10px;font-style:normal}.session-list i{height:4px;overflow:hidden;border-radius:4px;background:#e8eff5}.session-list i span{height:100%;border-radius:4px;background:linear-gradient(90deg,#0584f4,#20cfd6)}.session-list small{color:#7f91a4;text-align:right}.result-panel{min-width:0;flex:1;padding:15px 15px 0;border:1px solid #e1eaf3;border-radius:12px;background:#fff;overflow:hidden}.filterbar{display:flex;align-items:center;gap:9px;margin-bottom:13px}.filterbar>.el-input{width:220px}.filterbar>.el-select{width:130px}.filter-spacer{flex:1}.cast-banner{display:flex;align-items:center;gap:12px;margin-bottom:12px;padding:9px 12px;border:1px solid #bee8dc;border-radius:8px;background:#effbf7}.cast-banner>span{padding:4px 7px;border-radius:5px;color:#fff;background:#13ad72;font-size:10px;font-weight:700}.cast-banner div{flex:1}.cast-banner b,.cast-banner small{display:block}.cast-banner small{margin-top:3px;color:#799286;font-size:10px}.subtext{display:block;margin-top:5px;color:#93a0ae}.order{display:block;color:#198fe8}.rematch-tag{display:block;margin-top:3px;color:#e36b25;font-size:9px;font-style:normal}.submit-progress{padding:4px 8px;border-radius:13px;color:#d88417;background:#fff4dc}.submit-progress.done{color:#11985c;background:#e8f9f0}.total{color:#096fc8;font-size:18px}.total.dq{color:#e54c4c}.muted{color:#9daab6}.correction-page{margin:16px;padding:16px;border:1px solid #e1eaf3;border-radius:12px;background:#fff;overflow:hidden}.correction-filter>.el-input{width:280px}.detail-summary{display:grid;grid-template-columns:2fr 1fr 1fr;gap:12px;margin-bottom:18px}.detail-summary>div{padding:14px;border-radius:9px;background:#f2f7fc}.detail-summary small,.detail-summary b{display:block}.detail-summary small{color:#8ea0b1}.detail-summary b{margin-top:6px;font-size:18px}.dialog-desc{margin-top:16px}.reason-form{margin-top:16px}
|
||||
@media(max-width:1000px){.workbench-aside{width:190px}.session-panel{width:235px}.top-actions .live-dot{display:none}}
|
||||
</style>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.workbench-table { border-radius: 9px 9px 0 0; }
|
||||
.workbench-table :deep(.el-table__header-wrapper th) {
|
||||
height: 46px;
|
||||
color: #52647b;
|
||||
background: #f6f8fb;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.workbench-table :deep(.el-table__row td) { height: 60px; border-bottom-color: #eef2f7; }
|
||||
.workbench-table :deep(.el-table__row:hover > td),
|
||||
.workbench-table :deep(.current-row > td) { background: #f5f9ff !important; }
|
||||
.workbench-table :deep(.el-table-fixed-column--right) { box-shadow: -8px 0 18px rgba(27, 52, 84, 0.035); }
|
||||
|
||||
.subtext { margin-top: 4px; color: #8796aa; font-size: 11px; }
|
||||
.submit-progress { display: inline-flex; min-width: 42px; box-sizing: border-box; justify-content: center; font-weight: 600; }
|
||||
.total { font-size: 19px; font-variant-numeric: tabular-nums; }
|
||||
|
||||
.row-actions {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.row-actions :deep(.el-button) { margin-left: 0; border-radius: 7px; }
|
||||
.row-actions :deep(.el-button--small) { height: 30px; padding: 5px 10px; }
|
||||
.row-actions :deep(.el-button--primary) { box-shadow: 0 4px 10px rgba(22, 119, 255, 0.16); }
|
||||
.row-actions :deep(.el-button--success) { box-shadow: 0 4px 10px rgba(39, 174, 96, 0.14); }
|
||||
.dropdown-arrow { margin-left: 2px; color: #8695a8; font-size: 12px; }
|
||||
:global(.danger-menu-item) { color: #e5484d !important; }
|
||||
|
||||
.correction-page { margin: 18px; padding: 0; }
|
||||
.correction-page > .filterbar { border-radius: 14px 14px 0 0; }
|
||||
.detail-summary > div { border: 1px solid #e7edf5; background: linear-gradient(145deg, #f5f9fd, #fbfdff); }
|
||||
|
||||
@media (max-width: 1360px) {
|
||||
.session-panel { width: 250px; }
|
||||
.filterbar { align-items: flex-start; flex-wrap: wrap; }
|
||||
.filter-spacer { display: none; }
|
||||
.toolbar-actions { margin-left: auto; }
|
||||
.filter-fields > :deep(.el-input) { width: 220px; }
|
||||
}
|
||||
|
||||
@media (max-width: 1080px) {
|
||||
.topbar { padding: 0 16px; }
|
||||
.topbar p { max-width: 330px; }
|
||||
.top-actions { gap: 5px; }
|
||||
.workspace { padding: 12px; gap: 12px; }
|
||||
.session-panel { width: 220px; padding: 14px 12px; }
|
||||
.result-heading small { display: none; }
|
||||
.filter-fields { flex-wrap: wrap; }
|
||||
.toolbar-actions { width: 100%; padding: 8px 0 0; border-left: 0; border-top: 1px solid #edf1f6; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.workbench-page {
|
||||
--wb-primary: #1677ff;
|
||||
--wb-text: #182b49;
|
||||
--wb-muted: #718198;
|
||||
--wb-line: #e7edf5;
|
||||
background: #f4f7fb;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
height: 84px;
|
||||
padding: 0 24px;
|
||||
border-bottom-color: var(--wb-line);
|
||||
box-shadow: 0 2px 12px rgba(24, 52, 89, 0.04);
|
||||
}
|
||||
|
||||
.topbar h1 { color: var(--wb-text); font-size: 23px; font-weight: 700; }
|
||||
.topbar p { max-width: 560px; overflow: hidden; color: var(--wb-muted); text-overflow: ellipsis; white-space: nowrap; }
|
||||
.top-actions { gap: 8px; }
|
||||
.top-actions :deep(.el-button) { margin-left: 0; border-color: #dfe7f1; border-radius: 8px; }
|
||||
|
||||
.live-dot {
|
||||
margin-right: 4px;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid #cceedd;
|
||||
border-radius: 999px;
|
||||
background: #f0fbf6;
|
||||
font-weight: 600;
|
||||
}
|
||||
.live-dot.offline { border-color: #ffd5d5; background: #fff3f3; }
|
||||
|
||||
.workspace { padding: 18px; gap: 16px; }
|
||||
.session-panel, .result-panel, .correction-page {
|
||||
border-color: var(--wb-line);
|
||||
border-radius: 14px;
|
||||
box-shadow: 0 8px 28px rgba(31, 55, 88, 0.055);
|
||||
}
|
||||
|
||||
.session-panel { width: 292px; padding: 18px 16px; }
|
||||
.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;
|
||||
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;
|
||||
}
|
||||
.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 > 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; }
|
||||
|
||||
.result-panel { display: flex; flex-direction: column; padding: 0; }
|
||||
.result-heading {
|
||||
min-height: 64px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 18px;
|
||||
border-bottom: 1px solid #edf1f6;
|
||||
}
|
||||
.result-heading > div:first-child { display: flex; align-items: center; min-width: 0; gap: 9px; }
|
||||
.result-heading b { overflow: hidden; color: var(--wb-text); font-size: 16px; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.result-heading span { flex: none; padding: 4px 8px; border-radius: 6px; color: #176fc1; background: #eaf5ff; font-size: 11px; }
|
||||
.result-heading small { color: var(--wb-muted); font-size: 12px; }
|
||||
.entry-count { flex: none; display: flex; align-items: baseline; gap: 6px; }
|
||||
.entry-count b { color: var(--wb-primary); font-size: 20px; }
|
||||
|
||||
.filterbar {
|
||||
min-height: 58px;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 10px 16px;
|
||||
border-bottom: 1px solid #edf1f6;
|
||||
background: #fbfcfe;
|
||||
}
|
||||
.filter-fields, .toolbar-actions { display: flex; align-items: center; gap: 8px; }
|
||||
.filter-fields > :deep(.el-input) { width: 250px; }
|
||||
.filter-fields > :deep(.el-select) { width: 142px; }
|
||||
.filterbar :deep(.el-button) { margin-left: 0; border-radius: 8px; }
|
||||
.toolbar-actions { padding-left: 12px; border-left: 1px solid #e4eaf2; }
|
||||
|
||||
.cast-banner {
|
||||
margin: 12px 16px 0;
|
||||
padding: 10px 12px;
|
||||
border-color: #b9e7d5;
|
||||
border-radius: 9px;
|
||||
background: linear-gradient(90deg, #effbf6, #f7fcff);
|
||||
}
|
||||
.cast-banner > span { box-shadow: 0 3px 10px rgba(19, 173, 114, 0.2); }
|
||||
|
||||
.table-wrap { min-height: 0; flex: 1; padding: 10px 10px 0; }
|
||||
|
||||
@media (max-width: 1360px) {
|
||||
.session-panel { width: 250px; }
|
||||
.filterbar { align-items: flex-start; flex-wrap: wrap; }
|
||||
.filter-spacer { display: none; }
|
||||
.toolbar-actions { margin-left: auto; }
|
||||
.filter-fields > :deep(.el-input) { width: 220px; }
|
||||
}
|
||||
|
||||
@media (max-width: 1080px) {
|
||||
.topbar { padding: 0 16px; }
|
||||
.topbar p { max-width: 330px; }
|
||||
.top-actions { gap: 5px; }
|
||||
.workspace { padding: 12px; gap: 12px; }
|
||||
.session-panel { width: 220px; padding: 14px 12px; }
|
||||
.result-heading small { display: none; }
|
||||
.filter-fields { flex-wrap: wrap; }
|
||||
.toolbar-actions { width: 100%; padding: 8px 0 0; border-left: 0; border-top: 1px solid #edf1f6; }
|
||||
}
|
||||
</style>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user