裁判报名二维码

This commit is contained in:
马梓彭 2026-09-17 11:53:46 +08:00
parent 1c50b88052
commit 7d258fd3ca
3 changed files with 21 additions and 9 deletions

View File

@ -4,4 +4,4 @@ export const judgeAssignmentBoardGet = (eventId) => http.get(`/judgeassignment/b
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}`);
export const judgeRegistrationCodePost = (eventId) => http.post(`/judgeassignment/registration-qrcode/${eventId}`);

View File

@ -7,7 +7,7 @@
<div v-if="mode === 'eventReferees'" class="panel-body">
<ProTable :data="eventReferees" :columns="eventRefereeColumns" :pagination="false" :tool-button="false" :loading="loading" row-key="eventRefereeId">
<template #tools><el-button type="success" icon="ele-Postcard" :loading="codeLoading" @click="generateCode">生成裁判报名码</el-button></template>
<template #tools><el-button type="success" icon="ele-Postcard" :loading="codeLoading" @click="generateCode">生成裁判报名二维码</el-button></template>
<template #certificateUrl="scope"><el-image v-if="scope.row.certificateUrl" :src="scope.row.certificateUrl" :preview-src-list="[scope.row.certificateUrl]" preview-teleported fit="cover" class="certificate" /><span v-else>-</span></template>
<template #roles="scope">{{ scope.row.roleNames?.join(' / ') || '-' }}</template>
</ProTable>
@ -50,15 +50,14 @@
</el-dialog>
<el-dialog v-model="codeVisible" title="裁判报名二维码" width="min(420px, 92vw)" align-center destroy-on-close>
<div class="code-dialog"><div ref="qrcodeRef" class="qrcode" aria-label="裁判报名二维码"></div><div class="registration-code">报名码:{{ registrationCode }}</div><el-button type="primary" @click="downloadCode">下载二维码</el-button></div>
<div class="code-dialog"><div class="qrcode"><img v-if="registrationCodeImage" :src="registrationCodeImage" alt="裁判报名小程序码" /></div><div class="scan-tip">微信扫码后将直接打开小程序赛事详情,并进入裁判报名流程</div><div class="registration-code">报名码:{{ registrationCode }}</div><el-button type="primary" @click="downloadCode">下载二维码</el-button></div>
</el-dialog>
</div>
</template>
<script setup>
import { computed, nextTick, reactive, ref, watch } from 'vue';
import { computed, reactive, ref, watch } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import QRCode from 'qrcodejs2-fixes';
import ProTable from '/@/components/ProTable/index.vue';
import { judgeAssignmentBoardGet, judgeAssignmentSavePost, judgeAssignmentSubmitPost, judgeCandidatesGet, judgeEventRefereesGet, judgeRegistrationCodePost } from '/@/api/match/judgeassignment';
@ -77,7 +76,7 @@ const assignmentMap = reactive({});
const codeVisible = ref(false);
const codeLoading = ref(false);
const registrationCode = ref('');
const qrcodeRef = ref();
const registrationCodeImage = ref('');
const eventRefereeColumns = [
{ prop: 'index', label: '序号', type: 'index', width: 80 },
@ -143,9 +142,18 @@ async function saveAssignments() {
async function submitAll() { await ElMessageBox.confirm('提交后分配任务即可同步到裁判端,是否继续?', '提交确认', { type: 'warning' }); const result = await judgeAssignmentSubmitPost({ eventId: Number(props.eventId) }); if (!result?.succeeded) return; ElMessage.success('提交成功'); load(); }
async function generateCode() {
codeLoading.value = true;
try { const result = await judgeRegistrationCodePost(props.eventId); if (!result?.succeeded || !result.data) return ElMessage.error('报名码生成失败,请稍后重试'); registrationCode.value = result.data; codeVisible.value = true; await nextTick(); qrcodeRef.value.innerHTML = ''; new QRCode(qrcodeRef.value, { text: registrationCode.value, width: 240, height: 240, colorDark: '#1d2129', colorLight: '#ffffff', correctLevel: QRCode.CorrectLevel.H }); } finally { codeLoading.value = false; }
try {
const result = await judgeRegistrationCodePost(props.eventId);
const data = result?.data;
if (!result?.succeeded || !data?.registrationCode || !data?.qrCodeDataUrl) return ElMessage.error('裁判报名二维码生成失败,请稍后重试');
registrationCode.value = data.registrationCode;
registrationCodeImage.value = data.qrCodeDataUrl;
codeVisible.value = true;
} catch {
// 请求层已统一提示错误,此处仅收口事件 Promise,避免产生未处理异常。
} finally { codeLoading.value = false; }
}
function downloadCode() { const canvas = qrcodeRef.value?.querySelector('canvas'); const image = qrcodeRef.value?.querySelector('img'); const url = canvas?.toDataURL('image/png') || image?.src; if (!url) return ElMessage.error('二维码尚未生成,请稍后重试'); const link = document.createElement('a'); link.href = url; link.download = `裁判报名二维码-${registrationCode.value}.png`; document.body.appendChild(link); link.click(); document.body.removeChild(link); }
function downloadCode() { if (!registrationCodeImage.value) return ElMessage.error('二维码尚未生成,请稍后重试'); const link = document.createElement('a'); link.href = registrationCodeImage.value; link.download = `裁判报名二维码-${registrationCode.value}.jpg`; document.body.appendChild(link); link.click(); document.body.removeChild(link); }
watch(() => props.active, value => value && load(), { immediate: true });
</script>
@ -175,7 +183,8 @@ watch(() => props.active, value => value && load(), { immediate: true });
.candidate { width: calc(100% - 16px); margin: 8px 8px 0; }
.code-dialog { display: flex; flex-direction: column; align-items: center; gap: 16px; }
.qrcode { width: 264px; height: 264px; padding: 12px; background: #fff; border: 1px solid var(--el-border-color-lighter); border-radius: 8px; box-shadow: var(--el-box-shadow-light); }
.qrcode :deep(img), .qrcode :deep(canvas) { display: block; width: 240px !important; height: 240px !important; }
.qrcode img { display: block; width: 240px; height: 240px; }
.scan-tip { max-width: 300px; font-size: 13px; line-height: 1.6; color: var(--el-text-color-secondary); text-align: center; }
.registration-code { font-size: 14px; color: var(--el-text-color-secondary); word-break: break-all; }
@media (max-width: 768px) { .judge-panel { padding: 0; } .project-heading { flex-direction: column; align-items: flex-start; gap: 6px; } .assign-layout { grid-template-columns: 1fr; } .role-slots { grid-template-columns: 1fr; } .submit-bar { position: sticky; bottom: 0; z-index: 2; padding: 12px; background: var(--el-bg-color); } }
</style>

View File

@ -36,6 +36,9 @@ const viteConfig = defineConfig((mode: ConfigEnv) => {
port: env.VITE_PORT as unknown as number,
open: JSON.parse(env.VITE_OPEN),
hmr: true,
watch: {
ignored: ['**/[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]/**', '**/dist/**'],
},
proxy: {
'/api': {
target: env.VITE_API_TARGET || 'https://event.yuedongsports.com',