裁判库,裁判报名与分配
This commit is contained in:
parent
712998124a
commit
84c1078589
@ -91,7 +91,8 @@ export const eventPagePost = (data) => {
|
||||
* }>,
|
||||
* registrationLimit: {
|
||||
* individualProjectLimit: number,
|
||||
* teamProjectLimit: number
|
||||
* teamProjectLimit: number,
|
||||
* refereeLimit: number
|
||||
* },
|
||||
* feeConfig: {
|
||||
* chargeMode: string,
|
||||
@ -139,7 +140,7 @@ export const eventDetailGet = (id) => {
|
||||
* @param {string} [data.customConfigJson] - 自定义配置JSON
|
||||
* @param {Array<Object>} data.units - 参与单位列表
|
||||
* @param {Array<Object>} data.projects - 项目列表
|
||||
* @param {Object} data.registrationLimit - 限报配置
|
||||
* @param {Object} data.registrationLimit - 限报配置(含个人/团队项目限报数、裁判限报数)
|
||||
* @param {Object} data.feeConfig - 费用配置
|
||||
*/
|
||||
export const eventPost = (data) => {
|
||||
@ -172,7 +173,7 @@ export const eventPost = (data) => {
|
||||
* @param {string} [data.customConfigJson] - 自定义配置JSON
|
||||
* @param {Array<Object>} data.units - 参与单位列表
|
||||
* @param {Array<Object>} data.projects - 项目列表
|
||||
* @param {Object} data.registrationLimit - 限报配置
|
||||
* @param {Object} data.registrationLimit - 限报配置(含个人/团队项目限报数、裁判限报数)
|
||||
* @param {Object} data.feeConfig - 费用配置
|
||||
*/
|
||||
export const eventPut = (data) => {
|
||||
|
||||
@ -81,4 +81,6 @@ export const eventarrangementDeleteDelete = (data) => {
|
||||
*/
|
||||
export const eventarrangementSetstatusPatch = (data) => {
|
||||
return http.patch('/eventarrangement/setstatus', data);
|
||||
};
|
||||
};
|
||||
|
||||
export const eventarrangementConfirmPost = (eventid) => http.post(`/eventarrangement/confirm/${eventid}`);
|
||||
|
||||
8
src/api/match/judgeassignment.js
Normal file
8
src/api/match/judgeassignment.js
Normal file
@ -0,0 +1,8 @@
|
||||
import { http } from '../../utils/request';
|
||||
export const judgeApplicationsGet = (eventId) => http.get(`/judgeassignment/applications/${eventId}`);
|
||||
export const judgeApplicationDelete = (id) => http.delete('/judgeassignment/applications', { data: { id } });
|
||||
export const judgeAssignmentBoardGet = (eventId) => http.get(`/judgeassignment/board/${eventId}`);
|
||||
export const judgeCandidatesGet = (eventId, sessionId) => http.get(`/judgeassignment/candidates/${eventId}/${sessionId}`);
|
||||
export const judgeAssignmentSavePost = (data) => http.post('/judgeassignment/save', data);
|
||||
export const judgeAssignmentSubmitPost = (data) => http.post('/judgeassignment/submit', data);
|
||||
export const judgeRegistrationCodePost = (eventId) => http.post(`/judgeassignment/registration-code/${eventId}`);
|
||||
7
src/api/match/referee.js
Normal file
7
src/api/match/referee.js
Normal file
@ -0,0 +1,7 @@
|
||||
import { http } from '../../utils/request';
|
||||
|
||||
export const refereePageGet = (params) => http.get('/referee/page', { params });
|
||||
export const refereeAddPost = (data) => http.post('/referee', data);
|
||||
export const refereeUpdatePost = (data) => http.put('/referee', data);
|
||||
export const refereeDelete = (id) => http.delete('/referee/delete', { data: { id } });
|
||||
export const refereeAuditPost = (data) => http.post('/referee/audit', data);
|
||||
2
src/api/match/scorerule.js
Normal file
2
src/api/match/scorerule.js
Normal file
@ -0,0 +1,2 @@
|
||||
import { http } from '../../utils/request';
|
||||
export const scoreRuleListGet = () => http.get('/scorerule/list');
|
||||
80
src/views/match/manage/JudgeAssignmentPanel.vue
Normal file
80
src/views/match/manage/JudgeAssignmentPanel.vue
Normal file
@ -0,0 +1,80 @@
|
||||
<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>
|
||||
@ -141,6 +141,21 @@
|
||||
</el-form-item>
|
||||
<span v-else>{{ localFormData.registrationLimit.teamProjectLimit }}</span>
|
||||
</div>
|
||||
<div class="limit-summary__card limit-summary__card--full">
|
||||
<div class="limit-summary__label">
|
||||
<span class="required-star" v-if="!readonly">*</span>
|
||||
裁判限报配置
|
||||
</div>
|
||||
<!-- 裁判限报数即使有报名记录也可以编辑 -->
|
||||
<el-form-item v-if="!readonly"
|
||||
prop="registrationLimit.refereeLimit"
|
||||
:rules="[{ required: true, message: '请填写裁判限报数', trigger: 'change' }]"
|
||||
style="margin: 0"
|
||||
>
|
||||
<el-input-number v-model="localFormData.registrationLimit.refereeLimit" :min="0" :step="1" />
|
||||
</el-form-item>
|
||||
<span v-else>{{ localFormData.registrationLimit.refereeLimit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-form>
|
||||
@ -200,7 +215,7 @@ const isOnlyIndividual = computed(() => {
|
||||
const formRef = ref(null);
|
||||
const localFormData = ref({
|
||||
projects: [],
|
||||
registrationLimit: { individualProjectLimit: 1, teamProjectLimit: 1 },
|
||||
registrationLimit: { individualProjectLimit: 1, teamProjectLimit: 1, refereeLimit: 1 },
|
||||
});
|
||||
const genderList = ref(['全部', '男', '女'])
|
||||
|
||||
@ -332,7 +347,12 @@ const isSyncingFromParent = ref(false);
|
||||
|
||||
const normalizeFromParent = async (v) => {
|
||||
isSyncingFromParent.value = true;
|
||||
localFormData.value.registrationLimit = v?.registrationLimit ?? { individualProjectLimit: 1, teamProjectLimit: 1 };
|
||||
const parentLimit = v?.registrationLimit ?? {};
|
||||
localFormData.value.registrationLimit = {
|
||||
individualProjectLimit: parentLimit.individualProjectLimit ?? 1,
|
||||
teamProjectLimit: parentLimit.teamProjectLimit ?? 1,
|
||||
refereeLimit: parentLimit.refereeLimit ?? 1,
|
||||
};
|
||||
localFormData.value.projects = (v?.projects ?? []).map((p) => ({
|
||||
...p,
|
||||
limitCount: p?.limitCount ?? 0,
|
||||
@ -382,6 +402,7 @@ watch(
|
||||
const rules = computed(() => {
|
||||
const baseRules = {
|
||||
'registrationLimit.individualProjectLimit': [{ required: true, message: '请填写个人项目限报数', trigger: 'change' }],
|
||||
'registrationLimit.refereeLimit': [{ required: true, message: '请填写裁判限报数', trigger: 'change' }],
|
||||
};
|
||||
// 只有在不是仅允许个人报名时,才添加团队项目限报数的验证
|
||||
if (!isOnlyIndividual.value) {
|
||||
@ -492,6 +513,10 @@ defineExpose({ validate });
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.limit-summary__card--full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.limit-summary__label {
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
|
||||
@ -171,6 +171,7 @@ const formData = ref({
|
||||
registrationLimit: {
|
||||
individualProjectLimit: 1,
|
||||
teamProjectLimit: 1,
|
||||
refereeLimit: 1,
|
||||
},
|
||||
canEditProject: true, // 是否可以编辑项目(有报名记录时不能编辑)
|
||||
},
|
||||
@ -242,7 +243,11 @@ const openDialog = async (id) => {
|
||||
formData.value.units = { units: d.units ?? [] };
|
||||
formData.value.projectConfig = {
|
||||
projects: d.projects ?? [],
|
||||
registrationLimit: d.registrationLimit ?? { individualProjectLimit: 1, teamProjectLimit: 1 },
|
||||
registrationLimit: {
|
||||
individualProjectLimit: d.registrationLimit?.individualProjectLimit ?? 1,
|
||||
teamProjectLimit: d.registrationLimit?.teamProjectLimit ?? 1,
|
||||
refereeLimit: d.registrationLimit?.refereeLimit ?? 1,
|
||||
},
|
||||
canEditProject: d.canEditProject ?? true,
|
||||
};
|
||||
// 完全覆盖 feeConfig,不要保留旧数据
|
||||
@ -382,7 +387,7 @@ const handleReset = () => {
|
||||
formData.value.units = { units: [] };
|
||||
formData.value.projectConfig = {
|
||||
projects: [],
|
||||
registrationLimit: { individualProjectLimit: 1, teamProjectLimit: 1 },
|
||||
registrationLimit: { individualProjectLimit: 1, teamProjectLimit: 1, refereeLimit: 1 },
|
||||
};
|
||||
formData.value.feeConfig = {
|
||||
chargeMode: 0,
|
||||
|
||||
@ -164,9 +164,13 @@
|
||||
<div class="action-bar">
|
||||
<el-button type="default" @click="previewArrangement">预览</el-button>
|
||||
<el-button type="primary" @click="getArr">导出</el-button>
|
||||
<el-button type="success" @click="confirmArrangement">确认编排</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="裁判报名分配" name="judges">
|
||||
<JudgeAssignmentPanel :event-id="initParam.eventId" :active="activeName === 'judges'" />
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="检录员管理" name="checkin">
|
||||
<CheckinManager :event-id="initParam.eventId" :active="activeName === 'checkin'" />
|
||||
</el-tab-pane>
|
||||
@ -343,7 +347,7 @@ import { ref, reactive, computed, onMounted, watch, nextTick, h } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { Check, View, Download, Rank, Warning } from '@element-plus/icons-vue';
|
||||
import { eventDetailGet } from '/@/api/match/event';
|
||||
import { eventarrangementListGet, eventarrangementPut, eventarrangementExportPost, eventarrangementPreviewPost } from '/@/api/match/eventarrangement';
|
||||
import { eventarrangementListGet, eventarrangementPut, eventarrangementExportPost, eventarrangementPreviewPost, eventarrangementConfirmPost } from '/@/api/match/eventarrangement';
|
||||
import { eventregistrationPagePost, eventregistrationBatchauditPost, eventregistrationMaterialurlsPost, eventregistrationStatsGet, eventregistrationExportPost } from '/@/api/match/eventregistration';
|
||||
import { eventscorePageathletePost, eventscorePagecoachleaderPost, eventscoreImportscorePost, eventscoreDownloadtemplateGet, eventscoreExportcertificatePost, eventscoreIssuecertificatePost, eventscoreGetCertificateDetailGet, eventscoreIssuepointsPost } from '/@/api/match/eventscore';
|
||||
import { certificateListGet } from '/@/api/match/certificate';
|
||||
@ -352,6 +356,7 @@ import Step2Units from './Steps/Step2Units.vue';
|
||||
import Step3ProjectConfig from './Steps/Step3ProjectConfig.vue';
|
||||
import Step4FeeConfig from './Steps/Step4FeeConfig.vue';
|
||||
import CheckinManager from './CheckinManager.vue';
|
||||
import JudgeAssignmentPanel from './JudgeAssignmentPanel.vue';
|
||||
import ProTable from '/@/components/ProTable/index.vue';
|
||||
import PreviewImage from '/@/components/PreviewImage/index.vue';
|
||||
import Sortable from 'sortablejs';
|
||||
@ -432,6 +437,7 @@ const formData = ref({
|
||||
registrationLimit: {
|
||||
individualProjectLimit: 1,
|
||||
teamProjectLimit: 1,
|
||||
refereeLimit: 1,
|
||||
},
|
||||
},
|
||||
// 步骤4:费用与账户配置
|
||||
@ -491,7 +497,11 @@ const openDialog = async (id) => {
|
||||
formData.value.units = { units: d.units ?? [] };
|
||||
formData.value.projectConfig = {
|
||||
projects: d.projects ?? [],
|
||||
registrationLimit: d.registrationLimit ?? { individualProjectLimit: 1, teamProjectLimit: 1 },
|
||||
registrationLimit: {
|
||||
individualProjectLimit: d.registrationLimit?.individualProjectLimit ?? 1,
|
||||
teamProjectLimit: d.registrationLimit?.teamProjectLimit ?? 1,
|
||||
refereeLimit: d.registrationLimit?.refereeLimit ?? 1,
|
||||
},
|
||||
};
|
||||
formData.value.feeConfig = { ...formData.value.feeConfig, ...(d.feeConfig ?? {}) };
|
||||
console.log('formData.value', formData.value);
|
||||
@ -849,6 +859,15 @@ const onSave = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const confirmArrangement = async () => {
|
||||
await ElMessageBox.confirm('确认编排后需继续完成裁判分配,是否继续?', '重要提示', { type: 'warning', confirmButtonText: '继续' });
|
||||
const settingsWithSortIndex = eventlist.value.map((item,index)=>({ProjectId:item.projectId||item.id,VenueCount:item.venueCount,SortIndex:index+1}));
|
||||
const saveRes = await eventarrangementPut({eventId:initParam.value.id,settings:settingsWithSortIndex});
|
||||
if(!saveRes.succeeded) return;
|
||||
const res = await eventarrangementConfirmPost(initParam.value.id);
|
||||
if(res.succeeded) ElMessage.success(`编排已确认:${res.data.sessionCount}个场次,${res.data.entryCount}条出场记录`);
|
||||
}
|
||||
|
||||
//export default {
|
||||
const downloadByBlob = (blob, filename = '赛事编排.xlsx') => {
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
|
||||
@ -142,6 +142,12 @@
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="计分规则:" prop="scoreRuleId">
|
||||
<el-select v-model="formData.scoreRuleId" placeholder="请选择计分规则" style="width: 100%">
|
||||
<el-option v-for="rule in scoreRules" :key="rule.id" :label="rule.ruleName" :value="rule.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 备注 -->
|
||||
<el-form-item label="备注:" prop="remark">
|
||||
<el-input v-model="formData.remark" placeholder="请输入备注" type="textarea" :rows="3" maxlength="500" show-word-limit></el-input>
|
||||
@ -234,6 +240,7 @@
|
||||
<span class="duration-label">秒</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="计分规则:"><el-input v-model="detailData.scoreRuleName" disabled /></el-form-item>
|
||||
|
||||
<!-- 备注 -->
|
||||
<el-form-item label="备注:">
|
||||
@ -250,12 +257,15 @@
|
||||
</template>
|
||||
|
||||
<script setup name="project">
|
||||
import { defineAsyncComponent, ref, reactive } from 'vue';
|
||||
import { defineAsyncComponent, ref, reactive, onMounted } from 'vue';
|
||||
import { ElMessage, ElForm } from 'element-plus';
|
||||
import SysMenuApi from '/@/api/SysMenuApi';
|
||||
import { auths } from '/@/utils/authFunction';
|
||||
import ProTable from '/@/components/ProTable/index.vue';
|
||||
import { projectPageGet, projectAddPost, projectEditPut, projectDeleteDelete, projectDetailGet } from '/@/api/match/project';
|
||||
import { scoreRuleListGet } from '/@/api/match/scorerule';
|
||||
const scoreRules = ref([]);
|
||||
onMounted(async () => { const res = await scoreRuleListGet(); scoreRules.value = res.data || []; });
|
||||
|
||||
// 定义表单引用类型
|
||||
const formRef = ref();
|
||||
@ -301,6 +311,7 @@ const formData = ref({
|
||||
remark: '',
|
||||
durationMinutes: '',
|
||||
durationSeconds: '',
|
||||
scoreRuleId: null,
|
||||
groups: [
|
||||
{
|
||||
groupName: '',
|
||||
@ -394,6 +405,7 @@ const formRules = ref({
|
||||
completionCount: [{ required: true, validator: validateCompletionCount, trigger: ['blur', 'change'] }],
|
||||
completionCountRange: [{ required: true, validator: validateCompletionCount, trigger: ['blur', 'change'] }],
|
||||
fee: [{ required: true, message: '请输入费用', trigger: 'blur' }],
|
||||
scoreRuleId: [{ required: true, message: '请选择计分规则', trigger: 'change' }],
|
||||
duration: [
|
||||
{ required: true, validator: validateDuration, trigger: ['blur', 'change'] }
|
||||
],
|
||||
@ -443,6 +455,7 @@ const columns = ref([
|
||||
{ prop: 'fee', label: '费用(元)' },
|
||||
|
||||
{ prop: 'time', label: '时长' },
|
||||
{ prop: 'scoreRuleName', label: '计分规则' },
|
||||
|
||||
{ prop: 'remark', label: '备注' },
|
||||
|
||||
@ -465,6 +478,7 @@ const onOpenMenu = async (row) => {
|
||||
remark: '',
|
||||
durationMinutes: '',
|
||||
durationSeconds: '',
|
||||
scoreRuleId: null,
|
||||
groups: [
|
||||
{
|
||||
groupName: '',
|
||||
|
||||
92
src/views/match/referee/index.vue
Normal file
92
src/views/match/referee/index.vue
Normal file
@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<div class="layout-padding referee-page">
|
||||
<el-card shadow="never" class="search-card">
|
||||
<el-form :model="query" inline label-width="70px">
|
||||
<el-form-item label="裁判姓名"><el-input v-model="query.name" placeholder="请输入" clearable /></el-form-item>
|
||||
<el-form-item label="地区"><el-cascader v-model="query.region" :options="regionOptions" :props="regionProps" clearable placeholder="全部" /></el-form-item>
|
||||
<el-form-item label="级别"><el-select v-model="query.level" clearable placeholder="全部"><el-option v-for="item in levels" :key="item.value" v-bind="item" /></el-select></el-form-item>
|
||||
<el-form-item label="账号"><el-input v-model="query.mobile" placeholder="请输入" clearable /></el-form-item>
|
||||
<el-form-item label="裁判角色"><el-select v-model="query.role" clearable placeholder="全部"><el-option v-for="item in roles" :key="item.value" v-bind="item" /></el-select></el-form-item>
|
||||
<el-form-item label="审核状态"><el-select v-model="query.auditStatus" clearable placeholder="全部"><el-option v-for="item in auditOptions" :key="item.value" v-bind="item" /></el-select></el-form-item>
|
||||
<el-form-item label="生成时间"><el-date-picker v-model="query.dates" type="daterange" value-format="YYYY-MM-DD" start-placeholder="开始日期" end-placeholder="结束日期" /></el-form-item>
|
||||
<el-form-item><el-button type="primary" :icon="Search" @click="search">搜索</el-button><el-button :icon="Refresh" @click="reset">重置</el-button></el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="table-card">
|
||||
<el-button type="primary" :icon="Plus" @click="openAdd">新增</el-button>
|
||||
<el-table v-loading="loading" :data="rows" border stripe class="referee-table">
|
||||
<el-table-column prop="name" label="裁判姓名" min-width="100" />
|
||||
<el-table-column prop="regionName" label="地区" min-width="180" />
|
||||
<el-table-column prop="levelName" label="级别" min-width="130" />
|
||||
<el-table-column label="证件" width="90" align="center"><template #default="{ row }"><el-image class="certificate" :src="row.certificateUrl" :preview-src-list="[row.certificateUrl]" preview-teleported fit="cover" /></template></el-table-column>
|
||||
<el-table-column prop="mobile" label="账号" min-width="125" />
|
||||
<el-table-column label="裁判角色" min-width="220"><template #default="{ row }">{{ row.roleNames?.join(' / ') }}</template></el-table-column>
|
||||
<el-table-column label="审核状态" width="100"><template #default="{ row }"><el-tag :type="auditTag(row.auditStatus)">{{ auditText(row.auditStatus) }}</el-tag></template></el-table-column>
|
||||
<el-table-column prop="createdTime" label="生成时间" min-width="165" />
|
||||
<el-table-column label="操作" fixed="right" width="190"><template #default="{ row }"><el-button link type="primary" @click="openEdit(row)">编辑</el-button><el-button v-if="row.auditStatus === 1" link type="success" @click="audit(row, 2)">通过</el-button><el-button v-if="row.auditStatus === 1" link type="warning" @click="audit(row, 3)">拒绝</el-button><el-button link type="danger" @click="remove(row)">删除</el-button></template></el-table-column>
|
||||
</el-table>
|
||||
<el-pagination v-model:current-page="query.pageNo" v-model:page-size="query.pageSize" :total="total" :page-sizes="[10, 20, 50, 100]" layout="total, sizes, prev, pager, next, jumper" @change="load" />
|
||||
</el-card>
|
||||
|
||||
<el-dialog v-model="dialogVisible" :title="form.id ? '编辑裁判' : '新增裁判'" width="620px" destroy-on-close>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="105px">
|
||||
<el-form-item label="裁判姓名" prop="name"><el-input v-model="form.name" maxlength="64" /></el-form-item>
|
||||
<el-form-item label="地区" prop="region"><el-cascader ref="regionCascader" v-model="form.region" :options="regionOptions" :props="regionProps" class="full" /></el-form-item>
|
||||
<el-form-item label="级别" prop="level"><el-select v-model="form.level" class="full"><el-option v-for="item in levels" :key="item.value" v-bind="item" /></el-select></el-form-item>
|
||||
<el-form-item label="裁判证件" prop="certificateUrl"><UploadImg v-model:image-url="form.certificateUrl" width="90px" height="90px"><template #tip>支持 jpg/png,大小不超过 5MB</template></UploadImg></el-form-item>
|
||||
<el-form-item label="手机号(账号)" prop="mobile"><el-input v-model="form.mobile" maxlength="11" /></el-form-item>
|
||||
<el-form-item v-if="needCode" label="验证码" prop="verificationCode"><el-input v-model="form.verificationCode" class="code-input" maxlength="6" /><el-button :disabled="countdown > 0 || !phoneValid" @click="sendCode">{{ countdown ? `${countdown}s` : '获取验证码' }}</el-button></el-form-item>
|
||||
<el-form-item label="裁判角色" prop="roleValues"><el-select v-model="form.roleValues" multiple class="full" placeholder="请选择"><el-option v-for="item in roles" :key="item.value" v-bind="item" /></el-select></el-form-item>
|
||||
</el-form>
|
||||
<template #footer><el-button @click="dialogVisible = false">取消</el-button><el-button type="primary" :loading="saving" @click="save">确定</el-button></template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { Plus, Refresh, Search } from '@element-plus/icons-vue';
|
||||
import UploadImg from '/@/components/Upload/Img.vue';
|
||||
import { regionCitiesbyprovinceGet, regionDistrictsbycityGet, regionProvincesGet } from '/@/api/match/area';
|
||||
import { commonSmsSendVerificationCodePost } from '/@/api/match/common';
|
||||
import { refereeAddPost, refereeAuditPost, refereeDelete, refereePageGet, refereeUpdatePost } from '/@/api/match/referee';
|
||||
|
||||
const levels = [{ value: 1, label: '国际级裁判员' }, { value: 2, label: '国家级裁判员' }, { value: 3, label: '一级裁判员' }, { value: 4, label: '二级裁判员' }, { value: 5, label: '三级裁判员' }];
|
||||
const roles = [{ value: 1, label: '主裁' }, { value: 2, label: '表现裁判' }, { value: 4, label: '难度裁判' }, { value: 8, label: '创意编排(规定赛)' }, { value: 16, label: '完成质量(规定赛)' }];
|
||||
const auditOptions = [{ value: 1, label: '待审核' }, { value: 2, label: '已通过' }, { value: 3, label: '已拒绝' }];
|
||||
const query = reactive({ name: '', region: [], level: null, mobile: '', role: null, auditStatus: null, dates: [], pageNo: 1, pageSize: 10 });
|
||||
const rows = ref([]); const total = ref(0); const loading = ref(false); const dialogVisible = ref(false); const saving = ref(false); const formRef = ref(); const regionCascader = ref(); const regionOptions = ref([]); const originalMobile = ref(''); const countdown = ref(0);
|
||||
const emptyForm = () => ({ id: null, name: '', region: [], level: null, certificateUrl: '', mobile: '', verificationCode: '', roleValues: [] });
|
||||
const form = reactive(emptyForm());
|
||||
const phoneValid = computed(() => /^1[3-9]\d{9}$/.test(form.mobile));
|
||||
const needCode = computed(() => !form.id || form.mobile !== originalMobile.value);
|
||||
const rules = computed(() => ({ name: [{ required: true, message: '请输入裁判姓名', trigger: 'blur' }], region: [{ type: 'array', required: true, len: 3, message: '请选择完整地区', trigger: 'change' }], level: [{ required: true, message: '请选择级别', trigger: 'change' }], certificateUrl: [{ required: true, message: '请上传裁判证件', trigger: 'change' }], mobile: [{ required: true, pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }], verificationCode: needCode.value ? [{ required: true, message: '请输入验证码', trigger: 'blur' }] : [], roleValues: [{ type: 'array', required: true, min: 1, message: '至少选择一个裁判角色', trigger: 'change' }] }));
|
||||
const regionProps = { value: 'code', label: 'name', lazy: true, lazyLoad: async (node, resolve) => { const { level, value } = node; const data = level === 0 ? await regionProvincesGet() : level === 1 ? await regionCitiesbyprovinceGet({ provinceCode: value }) : await regionDistrictsbycityGet({ cityCode: value }); resolve((data?.data || data || []).map(x => ({ ...x, leaf: level === 2 }))); } };
|
||||
|
||||
async function load() { loading.value = true; try { const params = { ...query, region: undefined, dates: undefined, regionCode: query.region?.at(-1), startTime: query.dates?.[0], endTime: query.dates?.[1] }; const res = await refereePageGet(params); const data = res?.data || res; rows.value = data?.rows || []; total.value = data?.total || 0; } finally { loading.value = false; } }
|
||||
function search() { query.pageNo = 1; load(); }
|
||||
function reset() { Object.assign(query, { name: '', region: [], level: null, mobile: '', role: null, auditStatus: null, dates: [], pageNo: 1 }); load(); }
|
||||
function openAdd() { Object.assign(form, emptyForm()); originalMobile.value = ''; dialogVisible.value = true; }
|
||||
function openEdit(row) { Object.assign(form, { id: row.id, name: row.name, region: [row.provinceCode, row.cityCode, row.districtCode], level: row.level, certificateUrl: row.certificateUrl, mobile: row.mobile, verificationCode: '', roleValues: roles.filter(x => (row.roles & x.value) === x.value).map(x => x.value) }); originalMobile.value = row.mobile; dialogVisible.value = true; }
|
||||
async function sendCode() { await commonSmsSendVerificationCodePost({ phone: form.mobile }); ElMessage.success('验证码已发送'); countdown.value = 60; const timer = setInterval(() => { countdown.value -= 1; if (!countdown.value) clearInterval(timer); }, 1000); }
|
||||
async function save() { if (!await formRef.value.validate().catch(() => false)) return; const labels = regionCascader.value?.getCheckedNodes?.()[0]?.pathLabels || form.region; const payload = { ...form, provinceCode: form.region[0], provinceName: labels[0], cityCode: form.region[1], cityName: labels[1], districtCode: form.region[2], districtName: labels[2], roles: form.roleValues.reduce((sum, v) => sum | v, 0), roleValues: undefined, region: undefined }; saving.value = true; try { form.id ? await refereeUpdatePost(payload) : await refereeAddPost(payload); ElMessage.success('保存成功'); dialogVisible.value = false; load(); } finally { saving.value = false; } }
|
||||
async function audit(row, status) { await refereeAuditPost({ id: row.id, auditStatus: status }); ElMessage.success(status === 2 ? '审核通过' : '已拒绝'); load(); }
|
||||
async function remove(row) { await ElMessageBox.confirm(`确定删除裁判“${row.name}”吗?`, '删除确认', { type: 'warning' }); await refereeDelete(row.id); ElMessage.success('删除成功'); load(); }
|
||||
const auditText = value => auditOptions.find(x => x.value === value)?.label || '-';
|
||||
const auditTag = value => value === 2 ? 'success' : value === 3 ? 'danger' : 'warning';
|
||||
onMounted(async () => { const data = await regionProvincesGet(); regionOptions.value = (data?.data || data || []).map(x => ({ ...x, leaf: false })); load(); });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.referee-page { display: flex; flex-direction: column; gap: 12px; }
|
||||
.search-card :deep(.el-card__body) { padding-bottom: 2px; }
|
||||
.search-card .el-input, .search-card .el-select, .search-card .el-cascader { width: 180px; }
|
||||
.table-card { flex: 1; }
|
||||
.referee-table { margin: 14px 0; }
|
||||
.certificate { width: 48px; height: 36px; border-radius: 3px; }
|
||||
.el-pagination { justify-content: flex-end; }
|
||||
.full { width: 100%; }
|
||||
.code-input { width: calc(100% - 120px); margin-right: 10px; }
|
||||
</style>
|
||||
Loading…
x
Reference in New Issue
Block a user