293 lines
10 KiB
Vue
293 lines
10 KiB
Vue
<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>
|