YD_Event.Web/src/views/match/manage/CheckinManager.vue

299 lines
9.4 KiB
Vue

<template>
<div class="checkin-manager">
<el-alert
v-if="scheduleError"
:title="scheduleError"
type="warning"
show-icon
:closable="false"
class="schedule-alert"
/>
<ProTable ref="checkerTable" :request-api="requestCheckers" :columns="columns" :pagination="true" :page-size="10" :tool-button="false" row-key="taskId">
<template #tools><el-button type="primary" icon="ele-Plus" :disabled="checkerList.length >= 3" @click="openCreateDialog">新增检录员</el-button></template>
<template #checkinNo="scope"><el-tag type="primary">{{ checkinName(scope.row.checkinNo) }}</el-tag></template>
<template #coverage>全部秩序册列</template>
<template #venue="scope">{{ scope.row.venue || '-' }}</template>
<template #taskStatus="scope"><el-tag :type="scope.row.taskStatus === 1 ? 'success' : 'info'">{{ scope.row.taskStatus === 1 ? '进行中' : '未启用' }}</el-tag></template>
<template #action="scope"><el-button text type="primary" @click="openEditDialog(scope.row)">编辑</el-button><el-button text type="danger" @click="unbindChecker(scope.row)">解除绑定</el-button></template>
</ProTable>
<el-dialog
v-model="dialogVisible"
:title="form.checkerUserId ? '编辑检录员' : '新增检录员'"
width="min(600px, 94vw)"
:close-on-click-modal="false"
destroy-on-close
>
<el-form ref="formRef" :model="form" :rules="rules" label-width="110px">
<el-form-item label="姓名" prop="name">
<el-input v-model.trim="form.name" maxlength="32" placeholder="请输入检录员姓名" />
</el-form-item>
<el-form-item label="登录手机号" prop="mobile">
<el-input
v-model.trim="form.mobile"
maxlength="11"
placeholder="请输入11位手机号"
@blur="checkMobileAvailability"
/>
<div v-if="accountCheck.message" class="account-tip" :class="accountCheck.available ? 'success' : 'error'">
{{ accountCheck.message }}
</div>
</el-form-item>
<el-form-item :label="form.checkerUserId ? '重置密码' : '登录密码'" prop="password">
<el-input
v-model="form.password"
type="password"
show-password
maxlength="32"
:placeholder="form.checkerUserId ? '不修改请留空' : '请输入6-32位密码'"
/>
</el-form-item>
<el-form-item label="负责检次" prop="checkinNo">
<el-select v-model="form.checkinNo" placeholder="请选择检次" style="width: 100%" :disabled="!!form.checkerUserId">
<el-option
v-for="no in [1, 2, 3]"
:key="no"
:label="`${checkinName(no)}(全部秩序册列)`"
:value="no"
:disabled="assignedCheckinNumbers.includes(no) && no !== form.checkinNo"
/>
</el-select>
</el-form-item>
<el-form-item label="检录区域">
<el-input v-model.trim="form.venue" maxlength="128" placeholder="例如:一号检录区(选填)" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="saveChecker">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { reactive, ref, watch } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import ProTable from '/@/components/ProTable/index.vue';
import {
checkinAdminMobileAvailabilityGet,
checkinAdminCheckerSavePost,
checkinAdminCheckersGet,
checkinAdminCheckerUnbindPost,
checkinAdminEventOptionsGet,
} from '/@/api/match/checkin';
const props = defineProps({
eventId: { type: [Number, String], required: true },
active: { type: Boolean, default: false },
});
const formRef = ref();
const checkerTable = ref();
const saving = ref(false);
const dialogVisible = ref(false);
const checkerList = ref([]);
const assignedCheckinNumbers = ref([]);
const scheduleError = ref('');
const accountCheck = reactive({ available: null, message: '' });
const form = reactive({
checkerUserId: null,
name: '',
mobile: '',
password: '',
checkinNo: 1,
venue: '',
});
const columns = [
{ prop: 'index', label: '序号', type: 'index', width: 80 },
{ prop: 'name', label: '姓名', minWidth: 110 },
{ prop: 'mobile', label: '登录手机号', minWidth: 140 },
{ prop: 'checkinNo', label: '负责检次', width: 110 },
{ prop: 'coverage', label: '检录范围', minWidth: 150 },
{ prop: 'venue', label: '检录区域', minWidth: 130 },
{ prop: 'taskStatus', label: '状态', width: 100 },
{ prop: 'createdTime', label: '添加时间', minWidth: 170 },
{ prop: 'action', label: '操作', width: 180, fixed: 'right' },
];
const validatePassword = (_rule, value, callback) => {
if (!form.checkerUserId && !value) return callback(new Error('请输入登录密码'));
if (value && (value.length < 6 || value.length > 32)) return callback(new Error('密码必须为6-32位'));
callback();
};
const rules = {
name: [{ required: true, message: '请输入检录员姓名', trigger: 'blur' }],
mobile: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' },
],
password: [{ validator: validatePassword, trigger: 'blur' }],
checkinNo: [{ required: true, message: '请选择负责检次', trigger: 'change' }],
};
const checkinName = (no) => ({ 1: '一检', 2: '二检', 3: '三检' }[Number(no)] || `第${no}检`);
const requestCheckers = async ({ pageNo = 1, pageSize = 10 }) => {
if (!props.active || !props.eventId) return { data: { rows: [], total: 0, pageNo, pageSize } };
const res = await checkinAdminCheckersGet(props.eventId);
checkerList.value = res?.data || [];
const start = (pageNo - 1) * pageSize;
return { ...res, data: { rows: checkerList.value.slice(start, start + pageSize), total: checkerList.value.length, pageNo, pageSize } };
};
const loadCheckers = async () => checkerTable.value?.getTableList();
const loadEventOptions = async () => {
scheduleError.value = '';
try {
const res = await checkinAdminEventOptionsGet(props.eventId);
const data = res?.data || {};
assignedCheckinNumbers.value = data.assignedCheckinNumbers || [];
if (!(data.units || []).length) scheduleError.value = '当前赛事秩序册没有可检录列';
} catch (error) {
assignedCheckinNumbers.value = [];
scheduleError.value = '请先在当前赛事的秩序册管理中完成赛事编排,再添加检录员';
}
};
const resetForm = () => {
Object.assign(form, {
checkerUserId: null,
name: '',
mobile: '',
password: '',
checkinNo: Math.min(checkerList.value.length + 1, 3),
venue: '',
});
accountCheck.available = null;
accountCheck.message = '';
};
const openCreateDialog = async () => {
await loadEventOptions();
if (scheduleError.value) {
ElMessage.warning(scheduleError.value);
return;
}
resetForm();
dialogVisible.value = true;
};
const openEditDialog = async (row) => {
await loadEventOptions();
Object.assign(form, {
checkerUserId: row.checkerUserId,
name: row.name,
mobile: row.mobile,
password: '',
checkinNo: row.checkinNo,
venue: row.venue || '',
});
accountCheck.available = true;
accountCheck.message = '当前检录员手机号';
dialogVisible.value = true;
};
const checkMobileAvailability = async () => {
if (!/^1[3-9]\d{9}$/.test(form.mobile)) {
accountCheck.available = false;
accountCheck.message = '手机号格式不正确';
return false;
}
const res = await checkinAdminMobileAvailabilityGet(form.mobile, form.checkerUserId || undefined);
accountCheck.available = !!res?.data?.available;
accountCheck.message = res?.data?.message || '';
return accountCheck.available;
};
const saveChecker = async () => {
if (!formRef.value) return;
const valid = await formRef.value.validate().catch(() => false);
if (!valid) return;
const available = await checkMobileAvailability();
if (!available) return;
saving.value = true;
try {
const res = await checkinAdminCheckerSavePost({
checkerUserId: form.checkerUserId || undefined,
eventId: Number(props.eventId),
name: form.name,
mobile: form.mobile,
password: form.password || undefined,
checkinNo: form.checkinNo,
venue: form.venue || undefined,
});
if (res?.succeeded === false) return;
ElMessage.success(form.checkerUserId ? '检录员信息已更新' : '检录员已添加并绑定当前赛事');
dialogVisible.value = false;
await loadCheckers();
await loadEventOptions();
} finally {
saving.value = false;
}
};
const unbindChecker = async (row) => {
const confirmed = await ElMessageBox.confirm(`确认解除“${row.name}”与当前赛事的检录绑定吗?`, '解除绑定', {
type: 'warning',
confirmButtonText: '确认解除',
cancelButtonText: '取消',
}).catch(() => false);
if (!confirmed) return;
const res = await checkinAdminCheckerUnbindPost(row.taskId);
if (res?.succeeded === false) return;
ElMessage.success('已解除赛事绑定');
await loadCheckers();
await loadEventOptions();
};
watch(
() => [props.active, props.eventId],
([active]) => {
if (active && props.eventId) {
loadCheckers();
loadEventOptions();
}
},
{ immediate: true }
);
</script>
<style scoped>
.checkin-manager {
min-height: 100%;
padding: 0 2px 24px;
}
.schedule-alert {
margin-bottom: 16px;
}
.account-tip {
width: 100%;
margin-top: 4px;
font-size: 12px;
line-height: 18px;
}
.account-tip.success {
color: #67c23a;
}
.account-tip.error {
color: #f56c6c;
}
:deep(.el-dialog .el-input), :deep(.el-dialog .el-select) {
width: 100%;
}
@media (max-width: 768px) {
.checkin-manager { padding: 0 0 16px; }
}
</style>