diff --git a/scripts/workbench-business-contract-test.mjs b/scripts/workbench-business-contract-test.mjs index 72066dc..16318a0 100644 --- a/scripts/workbench-business-contract-test.mjs +++ b/scripts/workbench-business-contract-test.mjs @@ -40,10 +40,12 @@ const checks = [ ['SQL状态30为已投屏', sql, "WHEN 30 THEN N''已投屏''"], ['报名与秩序册数据源', launcher, "switchSource('orderbook')"], ['赛事列表分页', launcher, 'v-model:current-page="pageNo"'], - ['工作台保存赛事上下文', launcher, "sessionStorage.setItem('scoring-context'"], + ['启动页统一保存赛事上下文', launcher, 'saveScoringContext({eventId:Number(event.id)'], ['秩序册具有独立页面', orderBookPage, 'orderBookImportPost'], + ['秩序册自动解析已选赛事', orderBookPage, 'resolveScoringEvent(route.query)'], ['成绩管理与投屏使用独立菜单路由', menuSeed, '"Path": "/scoring/cast"'], ['秩序册直达路由具有兼容兜底', backEndRoutes, 'ensureScoringOperationalRoutes'], + ['旧秩序册菜单路由自动修正', backEndRoutes, 'normalizeScoringMenuRoutes(routeList)'], ['超级管理员具有秩序册菜单授权', roleMenuSeed, '"MenuId": 1088'], ['工作台动态评分列', workbench, 'v-for="col in detailColumns"'], ['规定赛隐藏难度', scoreScreen, "includes('PRESCRIBED')"], diff --git a/src/router/backEnd.ts b/src/router/backEnd.ts index 83fbbc4..eaa1de9 100644 --- a/src/router/backEnd.ts +++ b/src/router/backEnd.ts @@ -103,7 +103,10 @@ export async function initBackEndControlRoutes() { ]; // 工作台业务页需要携带赛事上下文,不能依赖菜单接口是否已经下发全部子菜单。 // 这些路由只作为隐藏的运行路由注册,左侧菜单仍以后端权限菜单为准。 - const routesWithScoringFallback = ensureScoringOperationalRoutes(routeList); + // 兼容已部署数据库里的旧菜单数据:旧版“秩序册”曾错误复用 launcher 路由。 + // 前端按稳定的路由名称/菜单编码修正运行地址,避免用户必须等待菜单种子重新入库。 + const normalizedRouteList = normalizeScoringMenuRoutes(routeList); + const routesWithScoringFallback = ensureScoringOperationalRoutes(normalizedRouteList); dynamicRoutes[0].children = await backEndComponent([...routesWithScoringFallback, ...staticRoute]); // dynamicRoutes[0].children = await backEndComponent(routeList); // 添加动态路由 @@ -166,6 +169,24 @@ export function getBackEndControlRoutes() { return SysMenuApi.getMenus(); } +function normalizeScoringMenuRoutes(routes: any[]) { + return routes.map((item) => { + const children = item?.children?.length ? normalizeScoringMenuRoutes(item.children) : item?.children; + const routeName = String(item?.name || item?.routeName || ''); + const menuCode = String(item?.code || item?.meta?.code || ''); + const title = String(item?.meta?.title || ''); + const isScoringOrderBookTitle = title === '秩序册' && String(item?.path || '').startsWith('/scoring/'); + const isOrderBook = routeName === 'scoringOrderBook' || menuCode === 'scoring:orderbook' || isScoringOrderBookTitle; + if (!isOrderBook) return children === item?.children ? item : { ...item, children }; + return { + ...item, + path: '/scoring/orderbook', + component: 'scoring/orderbook/index', + children, + }; + }); +} + function ensureScoringOperationalRoutes(routes: any[]) { const paths = new Set(); const collectPaths = (items: any[]) => items.forEach((item) => { diff --git a/src/views/scoring/launcher/index.vue b/src/views/scoring/launcher/index.vue index 3bf5f31..7a3ffc3 100644 --- a/src/views/scoring/launcher/index.vue +++ b/src/views/scoring/launcher/index.vue @@ -25,6 +25,7 @@ import { ref } from 'vue'; import { useRoute, useRouter } from 'vue-router'; import { workbenchEventsGet } from '/@/api/match/workbench'; +import { saveScoringContext } from '/@/utils/scoringContext'; const route=useRoute(), router=useRouter(), step=ref('mode'), sourceMode=ref('registration'), keyword=ref(''), events=ref([]), loading=ref(false), pageNo=ref(1), total=ref(0); const pageSize=8; @@ -35,7 +36,7 @@ if(route.query.sourceMode||route.query.eventId){sessionStorage.setItem('scoring- function switchSource(value){sourceMode.value=value;pageNo.value=1;loadEvents()} function searchEvents(){pageNo.value=1;loadEvents()} async function loadEvents(){loading.value=true;try{const res=await workbenchEventsGet({pageNo:pageNo.value,pageSize,eventName:keyword.value||undefined,sourceMode:sourceMode.value});const page=res?.data||{};events.value=page.rows||page.list||page.items||[];total.value=Number(page.total||page.totalCount||events.value.length)}finally{loading.value=false}} -function enterEvent(event){const context={eventId:Number(event.id),eventName:event.eventName,mode:'freestyle',sourceMode:sourceMode.value};sessionStorage.setItem('scoring-context',JSON.stringify(context));const path=sourceMode.value==='orderbook'?'/scoring/orderbook':'/scoring/workbench';router.push({path,query:{eventId:context.eventId,eventName:context.eventName}})} +function enterEvent(event){const context=saveScoringContext({eventId:Number(event.id),eventName:event.eventName,mode:'freestyle',sourceMode:sourceMode.value});const path=sourceMode.value==='orderbook'?'/scoring/orderbook':'/scoring/workbench';router.push({path,query:{eventId:context.eventId,eventName:context.eventName}})} function dateRange(event){const start=String(event.matchStartTime||'').slice(0,10),end=String(event.matchEndTime||'').slice(0,10);return start||end?`${start||'待定'} 至 ${end||'待定'}`:'比赛时间待定'} function eventStatusName(status){return({0:'未开始',1:'报名中',2:'报名结束',3:'比赛中',4:'已结束'})[status]||'待处理'} function eventStatusType(status){return({1:'warning',2:'info',3:'success',4:'info'})[status]||''} diff --git a/src/views/scoring/orderbook/index.vue b/src/views/scoring/orderbook/index.vue index 2572b91..d0bd513 100644 --- a/src/views/scoring/orderbook/index.vue +++ b/src/views/scoring/orderbook/index.vue @@ -19,10 +19,11 @@ import { ref } from 'vue'; import { useRoute, useRouter } from 'vue-router'; import { ElMessage, ElMessageBox } from 'element-plus'; import { orderBookTemplateGet, orderBookImportPost, orderBookConfirmPost } from '/@/api/match/workbench'; +import { resolveScoringEvent } from '/@/utils/scoringContext'; const route=useRoute(),router=useRouter(); -let saved={};try{saved=JSON.parse(sessionStorage.getItem('scoring-context')||'{}')}catch{saved={}} -const eventId=Number(route.query.eventId)||Number(saved.eventId)||0,eventName=String(route.query.eventName||saved.eventName||'未选择赛事'); +const scoringEvent=resolveScoringEvent(route.query); +const eventId=scoringEvent.eventId,eventName=scoringEvent.eventName; const file=ref(null),result=ref(null),parsing=ref(false),confirming=ref(false); if(!eventId)router.replace({path:'/scoring/launcher',query:{sourceMode:'orderbook'}}); function changeEvent(){router.push({path:'/scoring/launcher',query:{sourceMode:'orderbook'}})}