81 lines
8.8 KiB
Vue
81 lines
8.8 KiB
Vue
|
|
<template>
|
|||
|
|
<div class="judge-panel">
|
|||
|
|
<el-radio-group v-model="mode" @change="load">
|
|||
|
|
<el-radio-button value="applications">裁判报名</el-radio-button>
|
|||
|
|
<el-radio-button value="assignments">裁判分配</el-radio-button>
|
|||
|
|
</el-radio-group>
|
|||
|
|
|
|||
|
|
<div v-if="mode === 'applications'" class="panel-body">
|
|||
|
|
<div class="toolbar"><el-button type="success" @click="generateCode">生成裁判报名码</el-button></div>
|
|||
|
|
<el-table v-loading="loading" :data="applications" border>
|
|||
|
|
<el-table-column prop="name" label="裁判姓名" />
|
|||
|
|
<el-table-column prop="regionName" label="地区" min-width="170" />
|
|||
|
|
<el-table-column prop="levelName" label="级别" />
|
|||
|
|
<el-table-column label="封面" width="90"><template #default="{row}"><el-image :src="row.certificateUrl" :preview-src-list="[row.certificateUrl]" preview-teleported class="certificate" /></template></el-table-column>
|
|||
|
|
<el-table-column prop="mobile" label="手机号(账号)" width="130" />
|
|||
|
|
<el-table-column label="裁判角色" min-width="190"><template #default="{row}">{{ row.roleNames.join('/') }}</template></el-table-column>
|
|||
|
|
<el-table-column prop="createdTime" label="生成时间" width="170" />
|
|||
|
|
<el-table-column label="操作" width="80"><template #default="{row}"><el-button link type="danger" @click="removeApplication(row)">删除</el-button></template></el-table-column>
|
|||
|
|
</el-table>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div v-else class="panel-body" v-loading="loading">
|
|||
|
|
<el-empty v-if="!board.arrangementConfirmed" description="当前赛事尚未确认编排,请稍后再试" />
|
|||
|
|
<template v-else>
|
|||
|
|
<section v-for="project in board.projects" :key="project.eventProjectId" class="project-section">
|
|||
|
|
<div class="project-title">{{ project.projectName }}({{ project.ruleName }})</div>
|
|||
|
|
<el-table :data="project.sessions" border>
|
|||
|
|
<el-table-column prop="venueName" label="场地" />
|
|||
|
|
<el-table-column label="状态"><template #default="{row}"><el-tag :type="row.assigned ? 'success' : 'info'">{{ row.assigned ? '已分配' : '未分配' }}</el-tag></template></el-table-column>
|
|||
|
|
<el-table-column label="操作" width="180"><template #default="{row}"><el-button link type="primary" :disabled="board.submitted" @click="openAssign(project,row)">分配裁判</el-button><el-button link :disabled="!row.assigned" @click="openAssign(project,row,true)">查看详情</el-button></template></el-table-column>
|
|||
|
|
</el-table>
|
|||
|
|
</section>
|
|||
|
|
<div class="submit-bar"><el-button @click="load">刷新</el-button><el-button type="primary" :disabled="board.submitted" @click="submitAll">{{ board.submitted ? '已提交' : '提交' }}</el-button></div>
|
|||
|
|
</template>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<el-dialog v-model="assignVisible" title="分配裁判" width="860px" destroy-on-close>
|
|||
|
|
<div :class="['assign-layout',{readonly:viewOnly}]">
|
|||
|
|
<div class="role-area">
|
|||
|
|
<div v-for="section in roleSections" :key="section.title" class="role-section">
|
|||
|
|
<div class="role-title">{{ section.title }}</div>
|
|||
|
|
<div class="role-slots">
|
|||
|
|
<div v-for="slot in section.slots" :key="slot.code" :class="['role-slot',{active:selectedRole===slot.code,readonly:viewOnly}]" @click="selectRole(slot.code)">
|
|||
|
|
<strong>{{ slot.name }}</strong><el-tag v-if="assignmentMap[slot.code]" :closable="!viewOnly" @close.stop="clearRole(slot.code)">{{ candidateName(assignmentMap[slot.code]) }}</el-tag><span v-else>{{ viewOnly ? '未分配' : '点击后从右侧选择裁判' }}</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div v-if="!viewOnly" class="candidate-area"><div class="candidate-title">报名裁判</div><el-empty v-if="eligibleCandidates.length===0" description="暂无符合岗位资格的裁判" :image-size="60" /><el-button v-for="item in eligibleCandidates" :key="item.refereeId" class="candidate" @click="chooseCandidate(item)">{{ item.name }} / {{ item.mobile }}</el-button></div>
|
|||
|
|
</div>
|
|||
|
|
<template #footer><el-button @click="assignVisible=false">{{ viewOnly ? '关闭' : '取消' }}</el-button><el-button v-if="!viewOnly" type="primary" @click="saveAssignments">确定</el-button></template>
|
|||
|
|
</el-dialog>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script setup>
|
|||
|
|
import { computed, reactive, ref, watch } from 'vue';
|
|||
|
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
|||
|
|
import { judgeApplicationDelete, judgeApplicationsGet, judgeAssignmentBoardGet, judgeAssignmentSavePost, judgeAssignmentSubmitPost, judgeCandidatesGet, judgeRegistrationCodePost } from '/@/api/match/judgeassignment';
|
|||
|
|
const props = defineProps({ eventId: { type: [String, Number], required: true }, active: Boolean });
|
|||
|
|
const mode=ref('applications'), loading=ref(false), applications=ref([]), board=reactive({arrangementConfirmed:false,submitted:false,projects:[]});
|
|||
|
|
const assignVisible=ref(false), viewOnly=ref(false), currentProject=ref(), currentSession=ref(), candidates=ref([]), roleSections=ref([]), selectedRole=ref(''), assignmentMap=reactive({});
|
|||
|
|
const roleFlag = role => role==='T'?1:role==='P'?2:role==='CJ'?8:role==='CQ'?16:4;
|
|||
|
|
const eligibleCandidates=computed(()=>candidates.value.filter(x=>selectedRole.value && (x.roles & roleFlag(selectedRole.value))!==0));
|
|||
|
|
const candidateName=id=>candidates.value.find(x=>x.refereeId===id)?.name || currentSession.value?.assignments?.find(x=>x.refereeId===id)?.refereeName || id;
|
|||
|
|
async function load(){if(!props.active||!props.eventId)return;loading.value=true;try{if(mode.value==='applications'){const r=await judgeApplicationsGet(props.eventId);applications.value=r.data||[];}else{const r=await judgeAssignmentBoardGet(props.eventId);Object.assign(board,r.data||{arrangementConfirmed:false,submitted:false,projects:[]});}}finally{loading.value=false;}}
|
|||
|
|
async function generateCode(){const r=await judgeRegistrationCodePost(props.eventId);await ElMessageBox.alert(`裁判报名码:${r.data}`, '生成成功',{confirmButtonText:'复制'});await navigator.clipboard?.writeText(r.data);ElMessage.success('报名码已复制');}
|
|||
|
|
async function removeApplication(row){await ElMessageBox.confirm(`确定移除裁判“${row.name}”的报名吗?`,'提示',{type:'warning'});await judgeApplicationDelete(row.applicationId);load();}
|
|||
|
|
async function openAssign(project,session,readonly=false){viewOnly.value=readonly;currentProject.value=project;currentSession.value=session;roleSections.value=JSON.parse(project.judgeRoleSchemaJson||'[]');Object.keys(assignmentMap).forEach(k=>delete assignmentMap[k]);session.assignments.forEach(x=>assignmentMap[x.judgeRole]=x.refereeId);if(!readonly){const r=await judgeCandidatesGet(props.eventId,session.sessionId);candidates.value=r.data||[];}else{candidates.value=[];}selectedRole.value=roleSections.value[0]?.slots?.[0]?.code||'';assignVisible.value=true;}
|
|||
|
|
function selectRole(role){if(!viewOnly.value)selectedRole.value=role;}
|
|||
|
|
function chooseCandidate(item){if(!selectedRole.value)return;const occupied=Object.entries(assignmentMap).find(([role,id])=>id===item.refereeId&&role!==selectedRole.value);if(occupied)return ElMessage.warning('同一场次一名裁判只能分配一个岗位');assignmentMap[selectedRole.value]=item.refereeId;}
|
|||
|
|
function clearRole(role){delete assignmentMap[role];}
|
|||
|
|
async function saveAssignments(){const slots=roleSections.value.flatMap(x=>x.slots);if(slots.some(x=>!assignmentMap[x.code]))return ElMessage.warning('请完成全部裁判岗位分配');await judgeAssignmentSavePost({eventId:props.eventId,sessionId:currentSession.value.sessionId,assignments:slots.map(x=>({judgeRole:x.code,refereeId:assignmentMap[x.code]}))});ElMessage.success('分配已保存');assignVisible.value=false;load();}
|
|||
|
|
async function submitAll(){await ElMessageBox.confirm('提交后分配任务即可同步到裁判端,是否继续?','提交确认',{type:'warning'});await judgeAssignmentSubmitPost({eventId:props.eventId});ElMessage.success('提交成功');load();}
|
|||
|
|
watch(()=>props.active,v=>v&&load(),{immediate:true});
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style scoped>
|
|||
|
|
.judge-panel{padding:12px 28px}.panel-body{margin-top:18px}.toolbar{display:flex;justify-content:flex-end;margin-bottom:10px}.certificate{width:48px;height:36px}.project-section{margin-bottom:18px}.project-title{width:360px;padding:7px 14px;color:#fff;background:#409eff;border-radius:4px 4px 0 0}.submit-bar{text-align:center;margin-top:24px}.assign-layout{display:grid;grid-template-columns:1fr 190px;gap:12px}.assign-layout.readonly{grid-template-columns:1fr}.role-section{border:1px solid #c6e2ff;margin-bottom:12px}.role-title,.candidate-title{padding:7px;text-align:center;color:#fff;background:#409eff}.role-slots{display:grid;grid-template-columns:repeat(2,1fr)}.role-slot{min-height:64px;padding:10px;border:1px solid #e5e7eb;cursor:pointer;display:flex;flex-direction:column;gap:8px}.role-slot.active{background:#ecf5ff;border-color:#409eff}.role-slot.readonly{cursor:default}.role-slot span{color:#a8abb2}.candidate-area{border:1px solid #dcdfe6;padding-bottom:10px}.candidate{margin:8px 8px 0;width:calc(100% - 16px)}
|
|||
|
|
</style>
|