From 2521a0f7f897bc2e8a9dbf13b1acf4c767625938 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A9=AC=E6=A2=93=E5=BD=AD?= Date: Wed, 9 Sep 2026 14:10:46 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.development | 2 +- src/router/backEnd.ts | 6 +++ src/utils/scoringContext.ts | 57 +++++++++++++++++++++++++++ src/views/scoring/launcher/index.vue | 2 +- src/views/scoring/workbench/index.vue | 13 +++--- 5 files changed, 73 insertions(+), 7 deletions(-) create mode 100644 src/utils/scoringContext.ts diff --git a/.env.development b/.env.development index 48d56ab..ab679e6 100644 --- a/.env.development +++ b/.env.development @@ -5,4 +5,4 @@ ENV = development VITE_API_URL = /api # 本地后端接口 -VITE_API_TARGET = http://127.0.0.1:9997 +VITE_API_TARGET = http://8.153.108.90:9996 diff --git a/src/router/backEnd.ts b/src/router/backEnd.ts index 20002ed..83fbbc4 100644 --- a/src/router/backEnd.ts +++ b/src/router/backEnd.ts @@ -228,6 +228,12 @@ export function dynamicImport(dynamicViewsModules: Record, com return dynamicViewsModules[matchKey]; } if (matchKeys?.length > 1) { + const exactKey = matchKeys.find((key) => { + const k = key.replace(/..\/views|../, ''); + return k === `${component}.vue` || k === `/${component}.vue` || k === `${component}.tsx` || k === `/${component}.tsx`; + }); + if (exactKey) return dynamicViewsModules[exactKey]; return false; } } + diff --git a/src/utils/scoringContext.ts b/src/utils/scoringContext.ts new file mode 100644 index 0000000..a2bfe02 --- /dev/null +++ b/src/utils/scoringContext.ts @@ -0,0 +1,57 @@ +/** + * 打分系统工作台的赛事上下文。 + * 工作台各业务页(秩序册、成绩管理、成绩投屏)都要共用同一个赛事, + * 从菜单直接进入时地址栏没有 query,因此统一从 sessionStorage 读取。 + */ + +const CONTEXT_KEY = 'scoring-context'; +const MODE_KEY = 'scoring-mode'; + +export interface ScoringContext { + eventId: number; + eventName: string; + mode: string; + sourceMode: string; +} + +/** + * 读取已保存的赛事上下文,没有或格式损坏时返回空上下文 + */ +export function readScoringContext(): ScoringContext { + let saved: any = {}; + try { + saved = JSON.parse(sessionStorage.getItem(CONTEXT_KEY) || '{}') ?? {}; + } catch { + saved = {}; + } + return { + eventId: Number(saved.eventId) || 0, + eventName: String(saved.eventName || ''), + mode: String(saved.mode || sessionStorage.getItem(MODE_KEY) || ''), + sourceMode: String(saved.sourceMode || ''), + }; +} + +/** + * 合并写入赛事上下文,只覆盖传入的字段 + */ +export function saveScoringContext(patch: Partial) { + const next = { ...readScoringContext(), ...patch }; + sessionStorage.setItem(CONTEXT_KEY, JSON.stringify(next)); + if (next.mode) sessionStorage.setItem(MODE_KEY, next.mode); + return next; +} + +/** + * 解析当前页面使用的赛事:地址栏 query 优先(从工作台点进来时带参), + * 否则回退到 sessionStorage(从左侧菜单点进来时没有 query)。 + * 解析出的赛事会回写上下文,保证后续页面刷新仍能拿到。 + */ +export function resolveScoringEvent(query: Record) { + const saved = readScoringContext(); + const eventId = Number(query?.eventId) || saved.eventId; + if (!eventId) return { eventId: 0, eventName: '未选择赛事', sourceMode: saved.sourceMode }; + const eventName = String(query?.eventName || (eventId === saved.eventId ? saved.eventName : '') || '未选择赛事'); + saveScoringContext({ eventId, eventName, mode: saved.mode || 'freestyle' }); + return { eventId, eventName, sourceMode: saved.sourceMode }; +} diff --git a/src/views/scoring/launcher/index.vue b/src/views/scoring/launcher/index.vue index 246106c..3bf5f31 100644 --- a/src/views/scoring/launcher/index.vue +++ b/src/views/scoring/launcher/index.vue @@ -1,6 +1,6 @@ @@ -98,7 +97,7 @@ if(eventId.value)sessionStorage.setItem('scoring-context',JSON.stringify({...sav 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 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; @@ -112,6 +111,10 @@ const showDifficulty=computed(()=>!String(selectedSession.value?.scoreRuleCode|| 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 correctionStatuses=[{value:0,label:'已申请'},{value:10,label:'已批准'},{value:20,label:'已拒绝'},{value:30,label:'裁判修改中'},{value:40,label:'已完成'}]; +const correctionGroups=computed(()=>{const map=new Map();for(const s of correctionRow.value?.judgeScores||[]){if(s.judgeRoleType!==undefined&&!map.has(s.judgeRoleType)){map.set(s.judgeRoleType,{judgeRoleType:s.judgeRoleType,judgeRoleName:s.judgeRoleName||`${s.judgeRoleType}号类型`})}}return [...map.values()]}); +const correctionSheetOptions=computed(()=>(correctionRow.value?.judgeScores||[]).filter(s=>s.judgeRoleType===correctionForm.value.judgeRoleType)); +function isCorrectionSheetAvailable(x){return Boolean(x&&x.sheetStatus>=10&&x.sheetStatus<90&&!x.hasPendingCorrection)} +function onCorrectionRoleChange(){correctionForm.value.scoreSheetId=null} const tabTitle=computed(()=>({screen:'成绩投屏',results:'成绩管理',corrections:'成绩修正申请'})[activeTab.value]); const tabDescription=computed(()=>({screen:'选择已完成成绩投到现场大屏',results:'复核成绩、查看评分单并处理异常比赛',corrections:'审批裁判发起的修正申请并追踪处理进度'})[activeTab.value]); @@ -148,7 +151,7 @@ async function confirmOne(row){if(actionLoading.value)return;actionLoading.value 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;const firstRoleType=(row.judgeScores||[])[0]?.judgeRoleType??null;correctionForm.value={judgeRoleType:firstRoleType,scoreSheetId:null,reason:''};correctionVisible.value=true} 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}} 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:'关闭'})}