293 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="citizen-sports-sync layout-padding">
<el-alert
title="此页面仅供超级管理员使用。请先在服务端配置平台地址和密钥,预览确认无误后再上传。"
type="warning"
:closable="false"
show-icon
/>
<el-card class="mt16" shadow="never">
<template #header>
<div class="card-header">
<span>同步条件</span>
<el-button :loading="connectionTesting" @click="testConnection">测试平台连接</el-button>
</div>
</template>
<el-form ref="formRef" :model="form" :rules="rules" label-width="140px" class="sync-form">
<el-form-item label="跃动赛事" prop="eventId">
<el-select
v-model="form.eventId"
filterable
remote
reserve-keyword
:remote-method="searchEvents"
:loading="eventLoading"
placeholder="输入赛事名称搜索"
style="width: 100%"
@change="selectEvent"
>
<el-option v-for="item in eventOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="平台赛事名称" prop="platformEventName">
<el-input v-model="form.platformEventName" maxlength="100" placeholder="必须与一网通办申报名称完全一致" />
</el-form-item>
<el-form-item label="系列赛场次名称">
<el-input v-model="form.seriesName" maxlength="100" placeholder="没有系列赛时留空" />
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="previewLoading" @click="preview">预览报名数据</el-button>
<el-button
type="danger"
:disabled="!previewResult?.canUpload"
:loading="uploading"
@click="execute"
>
确认上传
</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card v-if="previewResult" class="mt16" shadow="never">
<template #header>数据预览</template>
<el-descriptions :column="4" border>
<el-descriptions-item label="跃动赛事">{{ previewResult.eventName }}</el-descriptions-item>
<el-descriptions-item label="报名人数">{{ previewResult.registrationCount }}</el-descriptions-item>
<el-descriptions-item label="待上传人数">{{ previewResult.uploadRecordCount }}</el-descriptions-item>
<el-descriptions-item label="组别数">{{ previewResult.typeCount }}</el-descriptions-item>
</el-descriptions>
<el-table :data="previewResult.types || []" border max-height="300" class="mt16">
<el-table-column prop="typeName" label="平台组别" min-width="200" />
<el-table-column prop="playerCount" label="人数" width="120" />
</el-table>
<el-collapse v-model="activePreviewPanels" class="detail-collapse">
<el-collapse-item name="players">
<template #title>
<span class="detail-collapse-title">待上传人员明细({{ previewResult.uploadRecordCount }} 人)</span>
</template>
<el-table :data="previewResult.players || []" border height="520">
<el-table-column type="index" label="序号" width="70" fixed="left" />
<el-table-column prop="typeName" label="平台组别" min-width="160" fixed="left" />
<el-table-column prop="playerName" label="姓名" width="110" fixed="left" />
<el-table-column prop="playerId" label="人员唯一ID" min-width="180" />
<el-table-column label="证件类型" width="110">
<template #default="scope">{{ formatCardType(scope.row.cardType) }}</template>
</el-table-column>
<el-table-column prop="cardNumber" label="证件号码" min-width="190" />
<el-table-column label="性别" width="80">
<template #default="scope">{{ formatSex(scope.row.playerSex) }}</template>
</el-table-column>
<el-table-column prop="playerPhone" label="手机号" width="140" />
<el-table-column prop="playerBirth" label="出生日期" width="120" />
<el-table-column prop="groupName" label="队伍名称" min-width="160" />
</el-table>
</el-collapse-item>
</el-collapse>
<el-alert
v-if="previewResult.canUpload"
class="mt16"
title="预检通过,可以上传"
type="success"
:closable="false"
show-icon
/>
<template v-else>
<el-alert class="mt16" title="预检未通过,请先修正以下报名资料" type="error" :closable="false" show-icon />
<el-table :data="previewResult.errors || []" border class="mt16">
<el-table-column prop="registrationPersonId" label="报名人员ID" width="190" />
<el-table-column prop="message" label="问题" min-width="320" />
</el-table>
</template>
</el-card>
<el-card v-if="executeResult" class="mt16" shadow="never">
<template #header>上传结果</template>
<el-descriptions :column="4" border>
<el-descriptions-item label="平台赛事编码">{{ executeResult.platformEventCode }}</el-descriptions-item>
<el-descriptions-item label="请求人数">{{ executeResult.requestedCount }}</el-descriptions-item>
<el-descriptions-item label="成功 / 失败">{{ executeResult.successCount }} / {{ executeResult.failureCount }}</el-descriptions-item>
<el-descriptions-item label="未尝试">{{ executeResult.unattemptedCount }}</el-descriptions-item>
</el-descriptions>
<el-alert
class="mt16"
:title="executeResult.aborted ? '上传中途终止,请根据批次结果核查,勿直接重复上传' : '上传完成'"
:type="executeResult.aborted ? 'error' : 'success'"
:closable="false"
show-icon
/>
<el-table :data="executeResult.batches || []" border class="mt16">
<el-table-column prop="typeName" label="组别" min-width="180" />
<el-table-column prop="requestedCount" label="请求" width="90" />
<el-table-column prop="successCount" label="成功" width="90" />
<el-table-column prop="failureCount" label="失败" width="90" />
<el-table-column label="状态" width="100">
<template #default="scope">
<el-tag :type="scope.row.success ? 'success' : 'danger'">{{ scope.row.success ? '成功' : '失败' }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="message" label="平台说明" min-width="260" />
</el-table>
</el-card>
</div>
</template>
<script setup name="citizenSportsSync">
import { onMounted, reactive, ref, watch } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { eventPagePost } from '/@/api/match/event';
import {
citizenSportsConnectionTestPost,
citizenSportsExecutePost,
citizenSportsPreviewPost,
} from '/@/api/match/citizenSportsSync';
const formRef = ref();
const form = reactive({
eventId: null,
platformEventName: '',
seriesName: '',
});
const rules = {
eventId: [{ required: true, message: '请选择跃动赛事', trigger: 'change' }],
platformEventName: [{ required: true, message: '请输入平台赛事名称', trigger: 'blur' }],
};
const eventOptions = ref([]);
const eventLoading = ref(false);
const connectionTesting = ref(false);
const previewLoading = ref(false);
const uploading = ref(false);
const previewResult = ref(null);
const executeResult = ref(null);
const activePreviewPanels = ref([]);
const formatCardType = (value) => ({ 1: '身份证', 2: '护照', 5: '户口本' })[value] || value || '-';
const formatSex = (value) => ({ 1: '男', 2: '女' })[value] || value || '-';
const requestInput = () => ({
eventId: form.eventId,
platformEventName: form.platformEventName.trim(),
seriesName: form.seriesName.trim() || null,
});
async function searchEvents(keyword = '') {
eventLoading.value = true;
try {
const response = await eventPagePost({ pageNo: 1, pageSize: 40, eventName: keyword || undefined });
eventOptions.value = (response?.data?.rows || []).map((item) => ({
value: Number(item.id ?? item.eventId),
label: item.eventName ?? item.EventName ?? `赛事 #${item.id ?? item.eventId}`,
}));
} finally {
eventLoading.value = false;
}
}
function selectEvent(eventId) {
const selected = eventOptions.value.find((item) => item.value === eventId);
if (selected) form.platformEventName = selected.label;
}
async function testConnection() {
connectionTesting.value = true;
try {
const response = await citizenSportsConnectionTestPost();
if (!response?.succeeded) return;
if (response.data?.success) ElMessage.success(response.data.message || '平台连接正常');
else ElMessage.error(response.data?.message || '平台连接失败');
} finally {
connectionTesting.value = false;
}
}
async function preview() {
if (!(await formRef.value?.validate().catch(() => false))) return;
previewLoading.value = true;
executeResult.value = null;
try {
const response = await citizenSportsPreviewPost(requestInput());
if (!response?.succeeded) return;
previewResult.value = response.data;
if (response.data?.canUpload) ElMessage.success('预检通过,请核对人数和组别后再确认上传');
} finally {
previewLoading.value = false;
}
}
async function execute() {
if (!previewResult.value?.canUpload) return;
const confirmed = await ElMessageBox.confirm(
`确认将 ${previewResult.value.uploadRecordCount} 人上传到“${form.platformEventName.trim()}”吗?`,
'确认上传',
{
type: 'warning',
confirmButtonText: '确认上传',
cancelButtonText: '取消',
}
)
.then(() => true)
.catch(() => false);
if (!confirmed) return;
uploading.value = true;
try {
const response = await citizenSportsExecutePost({ ...requestInput(), confirmUpload: true });
if (!response?.succeeded) return;
executeResult.value = response.data;
if (!response.data?.aborted) ElMessage.success('报名数据上传完成');
} finally {
uploading.value = false;
}
}
watch(
() => [form.eventId, form.platformEventName, form.seriesName],
() => {
previewResult.value = null;
executeResult.value = null;
}
);
onMounted(() => searchEvents());
</script>
<style scoped lang="scss">
.citizen-sports-sync {
display: block;
overflow-y: auto;
}
.mt16 {
margin-top: 16px;
}
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
}
.sync-form {
max-width: 760px;
}
.detail-collapse {
margin-top: 16px;
}
.detail-collapse-title {
font-weight: 600;
}
</style>