810 lines
26 KiB
Vue
810 lines
26 KiB
Vue
<template>
|
||
<div class="system-menu-container layout-padding">
|
||
<el-tabs v-model="activeTab" class="tabs" @tab-click="handleClick">
|
||
<el-tab-pane label="团队管理" name="first">
|
||
<ProTable ref="tableRefteam" :request-api="teamPageGet" :columns="columns" :pagination="true"
|
||
:tool-button="true">
|
||
<template #tools>
|
||
<el-button type="primary" icon="ele-Plus" @click="openTeam()"> 新增团队
|
||
</el-button>
|
||
</template>
|
||
<template #index="scope">
|
||
{{ scope.$index + 1 }}
|
||
</template>
|
||
|
||
<template #action="scope">
|
||
<el-button icon="ele-Edit" size="small" text type="primary" @click="openTeam(scope.row.id)">
|
||
编辑 </el-button>
|
||
<el-popconfirm title="确认删除此团队吗?" @confirm="onDeleteTeam(scope.row.id)">
|
||
<template #reference>
|
||
<el-button icon="ele-Delete" size="small" text type="danger">
|
||
删除
|
||
</el-button>
|
||
</template>
|
||
</el-popconfirm>
|
||
</template>
|
||
</ProTable>
|
||
</el-tab-pane>
|
||
<el-tab-pane label="运动员管理" name="second">
|
||
<ProTable ref="tableRefSport" :request-api="appuserAthletePageGet" :columns="columnsSport"
|
||
:pagination="true" :tool-button="true" :initParam="initParam">
|
||
<template #tools>
|
||
<el-button type="primary" icon="ele-Plus" @click="onOpenMenu()"> 新增
|
||
</el-button>
|
||
</template>
|
||
<template #index="scope">
|
||
{{ scope.$index + 1 }}
|
||
</template>
|
||
<template #idCardImageUrl="scope">
|
||
<el-image :src="scope.row.idCardImageUrl" style="width: 100px; " fit="cover" />
|
||
</template>
|
||
<template #action="scope">
|
||
<el-button icon="ele-Edit" size="small" text type="primary" @click="onOpenMenu(scope.row)">
|
||
编辑 </el-button>
|
||
<el-popconfirm title="确认删除此运动员吗?" @confirm="onDeleteMenu(scope.row.id)">
|
||
<template #reference>
|
||
<el-button icon="ele-Delete" size="small" text type="danger">
|
||
删除
|
||
</el-button>
|
||
</template>
|
||
</el-popconfirm>
|
||
</template>
|
||
</ProTable>
|
||
</el-tab-pane>
|
||
</el-tabs>
|
||
|
||
<!-- 团队新增弹窗 -->
|
||
<el-dialog :title="teamFormData.id ? '编辑团队' : '新增团队'" v-model="isShowTeamDialog" width="800px" style="min-width: 600px;">
|
||
<el-form ref="teamFormRef" :model="teamFormData" style="min-height: 250px;" :rules="teamFormRules" label-width="120px" v-if="isShowTeamDialog">
|
||
<el-form-item label="团队名称" prop="name">
|
||
<el-input v-model="teamFormData.name" placeholder="请输入团队名称" />
|
||
</el-form-item>
|
||
|
||
<el-form-item label="队员" prop="memberList">
|
||
<div>
|
||
<el-button type="primary" @click="openMemberDialog">添加队员</el-button>
|
||
<div style="margin-top:8px">
|
||
<template v-if="teamFormData.memberList && teamFormData.memberList.length">
|
||
<el-tag v-for="m in teamFormData.memberList" :key="m.id" closable @close="() => removeMember(m.id)" style="margin-right:6px">{{ m.name }}</el-tag>
|
||
</template>
|
||
<template v-else>
|
||
<span class="muted">未选择队员</span>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="领队" prop="leaderIds">
|
||
<el-select v-model="teamFormData.leaderIds" multiple placeholder="请选择领队">
|
||
<el-option v-for="opt in leaderOptions" :key="opt.value" :label="opt.label" :value="opt.value"></el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="教练" prop="coachIds">
|
||
<el-select v-model="teamFormData.coachIds" multiple placeholder="请选择教练">
|
||
<el-option v-for="opt in coachOptions" :key="opt.value" :label="opt.label" :value="opt.value"></el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<span class="dialog-footer">
|
||
<el-button @click="isShowTeamDialog=false">取消</el-button>
|
||
<el-button type="primary" @click="onTeamSubmit">确定</el-button>
|
||
</span>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 成员选择弹窗 -->
|
||
<el-dialog title="选择队员" v-model="isShowMemberDialog" width="900px">
|
||
<ProTable ref="memberTableRef" :request-api="appuserAthletePageGet" :columns="columnsMember" :initParam="initParam" :pagination="true" />
|
||
<template #footer>
|
||
<span class="dialog-footer">
|
||
<el-button @click="cancelMemberSelection">取消</el-button>
|
||
<el-button type="primary" @click="confirmMemberSelection">确定</el-button>
|
||
</span>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- 弹窗 -->
|
||
<el-dialog :title="dialogTitle" v-model="isShowDialog" width="width: 800px;">
|
||
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="200px" style="margin-bottom: 30px;min-height: 250rpx;"
|
||
v-if="isShowDialog">
|
||
|
||
<!-- 证件类型选择 -->
|
||
<el-form-item label="运动员身份证认证">
|
||
<el-radio-group v-model="ocrType">
|
||
<el-radio label="idcard">身份证认证</el-radio>
|
||
<el-radio label="passport">护照认证</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
|
||
<!-- 证件照片上传 -->
|
||
<el-form-item :label="ocrType === 'passport' ? '护照照片' : '身份证照片'" prop="idCardImageUrl">
|
||
<el-upload class="avatar-uploader" action="/api/file/upload" :show-file-list="false"
|
||
:on-success="handleAvatarSuccess" :before-upload="beforeAvatarUpload">
|
||
<img v-if="formData.idCardImageUrl" :src="formData.idCardImageUrl" class="avatar" style="height: 150px;" />
|
||
<el-icon v-else class="avatar-uploader-icon">
|
||
<Plus />
|
||
</el-icon>
|
||
</el-upload>
|
||
<div style="color: #909399; font-size: 12px; margin-top: 4px;">
|
||
请先选择证件类型,再上传对应的证件照片进行识别
|
||
</div>
|
||
</el-form-item>
|
||
|
||
|
||
<!-- 运动员姓名 -->
|
||
<el-form-item label="姓名" prop="name">
|
||
<el-input v-model="formData.name" disabled placeholder="请输入运动员姓名"></el-input>
|
||
</el-form-item>
|
||
|
||
<!-- 性别 -->
|
||
<el-form-item label="性别" prop="gender">
|
||
<el-tag v-if="formData.gender !== ''" :type="formData.gender === 0 ? '' : 'success'">
|
||
{{ formData.gender === 0 ? '男' : '女' }}
|
||
</el-tag>
|
||
<span v-else style="color: #909399;">请选择性别</span>
|
||
</el-form-item>
|
||
|
||
<el-form-item v-if="ocrType === 'idcard'" label="身份证号" prop="idCardNumber">
|
||
<el-input v-model="formData.idCardNumber" disabled placeholder="请输入身份证号"></el-input>
|
||
</el-form-item>
|
||
|
||
<el-form-item v-if="ocrType === 'passport'" label="出生日期" prop="birthDate">
|
||
<el-input v-model="formData.birthDate" placeholder="请选择出生日期" disabled></el-input>
|
||
</el-form-item>
|
||
|
||
<!-- 联系电话 -->
|
||
<el-form-item label="联系电话" prop="mobile">
|
||
<el-input v-model="formData.mobile" placeholder="请输入联系电话" style="width: 360px;"></el-input>
|
||
<el-button
|
||
v-if="needVerificationCode"
|
||
type="text"
|
||
@click="onSendCode"
|
||
style="margin-left: 8px;"
|
||
:disabled="sending || !isPhoneValid">
|
||
{{ sending ? `${sendTimer}s` : '获取验证码' }}
|
||
</el-button>
|
||
<el-tag v-else-if="formData.mobile && !isMobileChanged" type="info" style="margin-left: 8px;">手机号未变更</el-tag>
|
||
</el-form-item>
|
||
|
||
<!-- 验证码 -->
|
||
<el-form-item label="验证码" prop="verificationCode" v-if="needVerificationCode">
|
||
<el-input v-model="formData.verificationCode" placeholder="请输入验证码" maxlength="6" style="width: 360px;"></el-input>
|
||
</el-form-item>
|
||
|
||
</el-form>
|
||
<template #footer>
|
||
<span class="dialog-footer">
|
||
<el-button @click="onCancel">取消</el-button>
|
||
<el-button type="primary" @click="onSubmit">确定</el-button>
|
||
</span>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup name="sysMenu">
|
||
import { defineAsyncComponent, ref, reactive, nextTick, computed } 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 { teamPageGet, teamDetailGet, teamAddPost, teamDeleteDelete, teamUpdatePut } from '/@/api/match/unituser';
|
||
import { appuserAthletePageGet, appuserAthleteAddPost, appuserAthleteUpdatePut, appuserLeaderPageGet, appuserCoachPageGet, appuserMemberDeletePost } from '/@/api/match/appuser';
|
||
import { commonOcrRecognizeIdcardPost, commonOcrRecognizePassportPost, commonSmsSendVerificationCodePost } from '/@/api/match/common';
|
||
// 定义表单引用类型
|
||
const formRef = ref();
|
||
const activeTab = ref('first')
|
||
const getDefaultFormData = () => ({
|
||
id: undefined,
|
||
name: '',
|
||
idCardNumber: '',
|
||
gender: '',
|
||
idCardImageUrl: '',
|
||
birthDate: '',
|
||
mobile: '',
|
||
verificationCode: '',
|
||
});
|
||
const formData = ref(getDefaultFormData())
|
||
|
||
// 证件识别类型:idcard-身份证,passport-护照
|
||
const ocrType = ref('idcard')
|
||
|
||
const initParam = ref({
|
||
personType: 1,
|
||
refresh: true,
|
||
})
|
||
|
||
const formRules = computed(() => {
|
||
// 编辑运动员且原来有手机号,则手机号必填;否则非必填
|
||
const isMobileRequired = !!(formData.value.id && originalMobile.value);
|
||
|
||
const baseRules = {
|
||
name: [
|
||
{ required: true, message: '请输入运动员姓名', trigger: 'blur' }
|
||
],
|
||
idCardNumber: [
|
||
{ required: true, message: '请输入身份证号', trigger: 'change' }
|
||
],
|
||
gender: [
|
||
{ required: true, message: '请选择性别', trigger: 'change' }
|
||
],
|
||
mobile: isMobileRequired
|
||
? [
|
||
{ required: true, message: '请输入联系电话', trigger: 'blur' },
|
||
{
|
||
validator: (rule, value, cb) => {
|
||
const reg = /^1[3-9]\d{9}$/;
|
||
if (!value) return cb(new Error('请输入联系电话'));
|
||
if (!reg.test(value)) return cb(new Error('请输入正确的手机号'));
|
||
cb();
|
||
},
|
||
trigger: 'blur'
|
||
}
|
||
]
|
||
: [
|
||
{
|
||
validator: (rule, value, cb) => {
|
||
if (!value) return cb(); // 非必填,允许为空
|
||
const reg = /^1[3-9]\d{9}$/;
|
||
if (!reg.test(value)) return cb(new Error('请输入正确的手机号'));
|
||
cb();
|
||
},
|
||
trigger: 'blur'
|
||
}
|
||
],
|
||
idCardImageUrl: [
|
||
{ required: true, message: '请上传身份证照片', trigger: 'change' }
|
||
],
|
||
birthDate: [
|
||
{ required: true, message: '请选择出生日期', trigger: 'change' }
|
||
],
|
||
};
|
||
|
||
// 填写了手机号且手机号发生变更(新增或修改)时,验证码必填
|
||
if (needVerificationCode.value) {
|
||
baseRules.verificationCode = [
|
||
{ required: true, message: '请输入验证码', trigger: 'blur' },
|
||
{ len: 6, message: '验证码长度为6位', trigger: 'blur' }
|
||
];
|
||
}
|
||
|
||
return baseRules;
|
||
});
|
||
|
||
const columns = ref([
|
||
{ prop: 'index', label: '序号', align: 'center', },
|
||
{ prop: 'name', label: '团队名称', search: { el: 'input' } },
|
||
{ prop: 'leaderNames', label: '领队' },
|
||
{ prop: 'coachNames', label: '教练' },
|
||
{ prop: 'action', label: '操作', align: 'center', fixed: 'right' },
|
||
]);
|
||
|
||
const columnsSport = ref([
|
||
{ prop: 'index', label: '序号', align: 'center', },
|
||
{ prop: 'name', label: '运动员姓名', search: { el: 'input' } },
|
||
{ prop: 'age', label: '年龄', },
|
||
{
|
||
prop: 'gender', label: '性别', search: {
|
||
el: 'select',
|
||
},
|
||
enum: [
|
||
{ label: '全部', value: '' },
|
||
{ label: '男', value: 0 },
|
||
{ label: '女', value: 1 },
|
||
],
|
||
},
|
||
// { prop: 'idCardImageUrl', label: '身份证' },
|
||
{ prop: 'idCardNumber', label: '身份证号' },
|
||
{ prop: 'mobile', label: '联系电话', search: { el: 'input' } },
|
||
{ prop: 'action', label: '操作', align: 'center', fixed: 'right' },
|
||
]);
|
||
|
||
const isShowDialog = ref(false);
|
||
const dialogTitle = ref('新增');
|
||
const tableRefteam = ref(null);
|
||
|
||
// 验证码相关
|
||
const sending = ref(false);
|
||
const sendTimer = ref(0);
|
||
const sendInterval = ref(null);
|
||
|
||
const originalMobile = ref('');
|
||
const isPhoneValid = computed(() => {
|
||
const reg = /^1[3-9]\d{9}$/;
|
||
return reg.test(formData.value.mobile);
|
||
});
|
||
const isMobileChanged = computed(() => {
|
||
if (!formData.value.id) return true;
|
||
return formData.value.mobile !== originalMobile.value;
|
||
});
|
||
const needVerificationCode = computed(() => {
|
||
// 填写了手机号 且 手机号发生了变更(新增或修改),才需要验证码
|
||
return !!(formData.value.mobile && isMobileChanged.value);
|
||
});
|
||
|
||
// 团队新增弹窗相关
|
||
const isShowTeamDialog = ref(false);
|
||
const teamFormRef = ref();
|
||
const teamFormData = ref({
|
||
id: undefined,
|
||
name: '',
|
||
memberIds: [],
|
||
memberList: [], // [{id,name}]
|
||
leaderIds: [],
|
||
coachIds: [],
|
||
memberNames: '',
|
||
});
|
||
|
||
const validateMemberList = (rule, value, callback) => {
|
||
if (!teamFormData.value.memberList || teamFormData.value.memberList.length === 0) {
|
||
callback(new Error('请至少选择一名队员'));
|
||
} else {
|
||
callback();
|
||
}
|
||
};
|
||
|
||
const validateLeaderIds = (rule, value, callback) => {
|
||
if (!value || value.length === 0) {
|
||
callback(new Error('请选择领队'));
|
||
} else {
|
||
callback();
|
||
}
|
||
};
|
||
|
||
const validateCoachIds = (rule, value, callback) => {
|
||
if (!value || value.length === 0) {
|
||
callback(new Error('请选择教练'));
|
||
} else {
|
||
callback();
|
||
}
|
||
};
|
||
|
||
const teamFormRules = ref({
|
||
name: [{ required: true, message: '请输入团队名称', trigger: 'blur' }],
|
||
memberList: [{ required: true, validator: validateMemberList, trigger: ['change', 'blur'] }],
|
||
leaderIds: [{ required: true, validator: validateLeaderIds, trigger: ['change', 'blur'] }],
|
||
coachIds: [{ required: true, validator: validateCoachIds, trigger: ['change', 'blur'] }],
|
||
});
|
||
|
||
// 成员选择弹窗(使用 ProTable 的多选能力)
|
||
const isShowMemberDialog = ref(false);
|
||
const memberTableRef = ref(null);
|
||
const columnsMember = ref([
|
||
{ type: 'selection' },
|
||
{ prop: 'name', label: '姓名', align: 'center' },
|
||
{ prop: 'idCardNumber', label: '身份证号', align: 'center' },
|
||
{ prop: 'mobile', label: '联系电话', align: 'center' },
|
||
]);
|
||
|
||
// 领队/教练下拉选项
|
||
const leaderOptions = ref([]);
|
||
const coachOptions = ref([]);
|
||
|
||
const loadLeaderOptions = async () => {
|
||
try {
|
||
const res = await appuserLeaderPageGet({ PageNo: 1, PageSize: 100 , personType:3});
|
||
if (res && res.succeeded && res.data && Array.isArray(res.data.rows)) {
|
||
leaderOptions.value = res.data.rows.map((r) => ({ label: r.name, value: r.id }));
|
||
}
|
||
} catch (error) {
|
||
console.error('加载领队失败', error);
|
||
}
|
||
};
|
||
|
||
const loadCoachOptions = async () => {
|
||
try {
|
||
const res = await appuserCoachPageGet({ PageNo: 1, PageSize: 100, personType:2 });
|
||
if (res && res.succeeded && res.data && Array.isArray(res.data.rows)) {
|
||
coachOptions.value = res.data.rows.map((r) => ({ label: r.name, value: r.id }));
|
||
}
|
||
} catch (error) {
|
||
console.error('加载教练失败', error);
|
||
}
|
||
};
|
||
|
||
// 打开新增团队弹窗
|
||
const openTeam = async (id) => {
|
||
// reset
|
||
teamFormData.value = { id: undefined, name: '', memberIds: [], memberList: [], leaderIds: [], coachIds: [], memberNames: '' };
|
||
await Promise.all([loadLeaderOptions(), loadCoachOptions()]);
|
||
if (id) {
|
||
// 编辑:回显数据
|
||
try {
|
||
const res = await teamDetailGet(id);
|
||
if (res && res.succeeded && res.data) {
|
||
const d = res.data;
|
||
teamFormData.value.id = d.id;
|
||
teamFormData.value.name = d.name || '';
|
||
// 处理成员(后端返回 members 数组,包含 userId / name)
|
||
if (Array.isArray(d.members)) {
|
||
// 根据 personType 区分成员类型:1-运动员,2-教练,3-领队
|
||
const athletes = [];
|
||
const coachIds = [];
|
||
const leaderIds = [];
|
||
d.members.forEach((m) => {
|
||
const id = m.userId ?? m.id;
|
||
const name = m.name ?? m.realName ?? m.fullName ?? '';
|
||
const pt = m.personType ?? m.type ?? null;
|
||
if (pt === 1 || String(pt) === '1') {
|
||
athletes.push({ id, name });
|
||
} else if (pt === 2 || String(pt) === '2') {
|
||
coachIds.push(id);
|
||
} else if (pt === 3 || String(pt) === '3') {
|
||
leaderIds.push(id);
|
||
} else {
|
||
// 如果没有 personType,尝试根据标记字段推断(优先填到运动员)
|
||
athletes.push({ id, name });
|
||
}
|
||
});
|
||
teamFormData.value.memberList = athletes;
|
||
teamFormData.value.memberIds = athletes.map((m) => m.id);
|
||
teamFormData.value.memberNames = athletes.map((m) => m.name).join(',');
|
||
// 优先使用 members 中的领队/教练 id,否则回退到返回的 leaderIds/coaches 字段
|
||
teamFormData.value.leaderIds = leaderIds.length ? leaderIds : (d.leaderIds ?? d.leaders?.map((l) => l.id) ?? []);
|
||
teamFormData.value.coachIds = coachIds.length ? coachIds : (d.coachIds ?? d.coaches?.map((c) => c.id) ?? []);
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('加载团队详情失败', error);
|
||
}
|
||
}
|
||
isShowTeamDialog.value = true;
|
||
};
|
||
|
||
// 打开成员选择弹窗
|
||
const openMemberDialog = () => {
|
||
isShowMemberDialog.value = true;
|
||
// ProTable 会自动请求数据(requestAuto = true),清空以前选择
|
||
// 尝试在表格加载后预选已存在成员
|
||
nextTick(async () => {
|
||
try {
|
||
// 清空已有选择
|
||
memberTableRef.value?.clearSelection?.();
|
||
// 等待表格数据加载;尝试重试几次以兼容异步加载
|
||
const maxRetry = 6;
|
||
let attempt = 0;
|
||
const trySelect = () => {
|
||
const tableDataRef = memberTableRef.value?.tableData;
|
||
const tableData = tableDataRef && tableDataRef.value ? tableDataRef.value : tableDataRef || [];
|
||
const tableElRef = memberTableRef.value?.element;
|
||
const tableEl = tableElRef && tableElRef.value ? tableElRef.value : tableElRef;
|
||
if (Array.isArray(tableData) && tableData.length) {
|
||
// 对每个已选 memberId,在表格当前页中找到对应行并选中
|
||
teamFormData.value.memberIds.forEach((id) => {
|
||
const row = tableData.find((r) => r.id === id);
|
||
if (row && tableEl && typeof tableEl.toggleRowSelection === 'function') {
|
||
tableEl.toggleRowSelection(row, true);
|
||
}
|
||
});
|
||
return true;
|
||
}
|
||
return false;
|
||
};
|
||
while (attempt < maxRetry) {
|
||
if (trySelect()) break;
|
||
attempt++;
|
||
// 等待 200ms 再试
|
||
// eslint-disable-next-line no-await-in-loop
|
||
await new Promise((r) => setTimeout(r, 200));
|
||
}
|
||
} catch (e) {
|
||
console.error('预选成员失败', e);
|
||
}
|
||
});
|
||
};
|
||
|
||
const confirmMemberSelection = () => {
|
||
const selected = memberTableRef.value?.selectedList || [];
|
||
// 保持 memberList 为对象数组,方便删除
|
||
teamFormData.value.memberList = selected.map((s) => ({ id: s.id, name: s.name }));
|
||
teamFormData.value.memberIds = teamFormData.value.memberList.map((m) => m.id);
|
||
teamFormData.value.memberNames = teamFormData.value.memberList.map((m) => m.name).join(',');
|
||
isShowMemberDialog.value = false;
|
||
};
|
||
|
||
const removeMember = (id) => {
|
||
teamFormData.value.memberList = teamFormData.value.memberList.filter((m) => m.id !== id);
|
||
teamFormData.value.memberIds = teamFormData.value.memberList.map((m) => m.id);
|
||
teamFormData.value.memberNames = teamFormData.value.memberList.map((m) => m.name).join(',');
|
||
};
|
||
|
||
const cancelMemberSelection = () => {
|
||
isShowMemberDialog.value = false;
|
||
};
|
||
|
||
// 提交新增团队
|
||
const onTeamSubmit = () => {
|
||
if (!teamFormRef.value) return;
|
||
teamFormRef.value.validate(async (valid) => {
|
||
if (!valid) return;
|
||
try {
|
||
const payload = {
|
||
name: teamFormData.value.name,
|
||
memberIds: teamFormData.value.memberIds,
|
||
leaderIds: teamFormData.value.leaderIds,
|
||
coachIds: teamFormData.value.coachIds,
|
||
};
|
||
const res = teamFormData.value.id ? await teamUpdatePut({ id: teamFormData.value.id, ...payload }) : await teamAddPost(payload);
|
||
if (res && res.succeeded) {
|
||
ElMessage.success(teamFormData.value.id ? '更新团队成功' : '新增团队成功');
|
||
isShowTeamDialog.value = false;
|
||
// 刷新团队表格(ProTable 暴露的 getTableList)
|
||
try {
|
||
await nextTick();
|
||
tableRefteam.value?.getTableList?.();
|
||
} catch (e) {
|
||
// ignore
|
||
}
|
||
// 切回团队页签
|
||
activeTab.value = 'first';
|
||
} else {
|
||
ElMessage.error(res.errors || res.msg || (teamFormData.value.id ? '更新团队失败' : '新增团队失败'));
|
||
}
|
||
} catch (error) {
|
||
ElMessage.error(teamFormData.value.id ? '更新团队失败' : '新增团队失败');
|
||
console.error(error);
|
||
}
|
||
});
|
||
};
|
||
|
||
const onOpenMenu = (row) => {
|
||
// 重置验证码相关状态
|
||
if (sendInterval.value) {
|
||
clearInterval(sendInterval.value);
|
||
sendInterval.value = null;
|
||
}
|
||
sending.value = false;
|
||
sendTimer.value = 0;
|
||
|
||
dialogTitle.value = row ? '编辑' : '新增';
|
||
formData.value = getDefaultFormData();
|
||
ocrType.value = 'idcard';
|
||
|
||
// 保存原始手机号
|
||
originalMobile.value = '';
|
||
|
||
if (row && typeof row === 'object') {
|
||
formData.value = {
|
||
...getDefaultFormData(),
|
||
id: row.id,
|
||
name: row.name ?? '',
|
||
idCardNumber: row.idCardNumber ?? '',
|
||
gender: row.gender !== undefined && row.gender !== null ? Number(row.gender) : '',
|
||
idCardImageUrl: row.idCardImageUrl ?? '',
|
||
birthDate: row.birthDate ?? '',
|
||
mobile: row.mobile ?? row.mobile ?? '',
|
||
};
|
||
originalMobile.value = row.mobile ?? row.mobile ?? '';
|
||
}
|
||
isShowDialog.value = true;
|
||
};
|
||
|
||
//删除运动员
|
||
const onDeleteMenu = async (id) => {
|
||
try {
|
||
const res = await appuserMemberDeletePost(id);
|
||
if (res && res.succeeded) {
|
||
ElMessage.success('删除成功');
|
||
// 刷新运动员表格
|
||
try {
|
||
await nextTick();
|
||
initParam.value.refresh = !initParam.value.refresh;
|
||
} catch (e) {
|
||
// ignore
|
||
}
|
||
} else {
|
||
ElMessage.error(res.errors || res.msg || '删除失败');
|
||
}
|
||
} catch (e) {
|
||
ElMessage.error('删除失败');
|
||
console.error(e);
|
||
}
|
||
};
|
||
|
||
// 删除团队(使用正确的 team API)
|
||
const onDeleteTeam = async (id) => {
|
||
try {
|
||
const res = await teamDeleteDelete({ id });
|
||
if (res && res.succeeded) {
|
||
ElMessage.success('删除团队成功');
|
||
// 刷新团队表格
|
||
try {
|
||
await nextTick();
|
||
tableRefteam.value?.getTableList?.();
|
||
} catch (e) {
|
||
// ignore
|
||
}
|
||
} else {
|
||
ElMessage.error(res.errors || res.msg || '删除团队失败');
|
||
}
|
||
} catch (e) {
|
||
ElMessage.error('删除团队失败');
|
||
console.error(e);
|
||
}
|
||
};
|
||
|
||
|
||
|
||
const onSendCode = async () => {
|
||
if (!isPhoneValid.value) {
|
||
ElMessage.error('请输入正确的手机号');
|
||
return;
|
||
}
|
||
if (!isMobileChanged.value) {
|
||
ElMessage.warning('手机号未变更,无需获取验证码');
|
||
return;
|
||
}
|
||
sending.value = true;
|
||
sendTimer.value = 60;
|
||
try {
|
||
const res = await commonSmsSendVerificationCodePost({ phone: formData.value.mobile });
|
||
if (res.succeeded) {
|
||
ElMessage.success('验证码已发送');
|
||
sendInterval.value = setInterval(() => {
|
||
sendTimer.value -= 1;
|
||
if (sendTimer.value <= 0) {
|
||
clearInterval(sendInterval.value);
|
||
sending.value = false;
|
||
}
|
||
}, 1000);
|
||
} else {
|
||
ElMessage.error(res.errors || res.msg || '发送验证码失败');
|
||
sending.value = false;
|
||
}
|
||
} catch (err) {
|
||
sending.value = false;
|
||
ElMessage.error(err && err.message ? err.message : '发送验证码失败');
|
||
}
|
||
};
|
||
|
||
const handleAvatarSuccess = async (response,
|
||
uploadFile) => {
|
||
//response[0].url
|
||
const imageUrl = response[0].url;
|
||
const res = ocrType.value === 'passport'
|
||
? await commonOcrRecognizePassportPost({ imageUrl })
|
||
: await commonOcrRecognizeIdcardPost({ imageUrl });
|
||
if(res.succeeded){
|
||
if(ocrType.value === 'passport'){
|
||
formData.value.name = res.data.name
|
||
formData.value.idCardNumber = res.data.passportNumber
|
||
formData.value.gender = res.data.gender == 'Male' ? 0 : 1
|
||
formData.value.idCardImageUrl = imageUrl
|
||
formData.value.birthDate = res.data.issueDateYmd
|
||
}else{
|
||
formData.value.name = res.data.name
|
||
formData.value.idCardNumber = res.data.idCardNumber
|
||
formData.value.gender = res.data.gender == '男' ? 0 : 1
|
||
formData.value.idCardImageUrl = imageUrl
|
||
formData.value.birthDate = res.data.birthDate
|
||
}
|
||
|
||
}else{
|
||
ElMessage.error('请上传正确的身份证/护照')
|
||
}
|
||
console.log(res)
|
||
}
|
||
|
||
const beforeAvatarUpload = () => {
|
||
// if (rawFile.type !== 'image/jpeg') {
|
||
// ElMessage.error('Avatar picture must be JPG format!')
|
||
// return false
|
||
// } else if (rawFile.size / 1024 / 1024 > 2) {
|
||
// ElMessage.error('Avatar picture size can not exceed 2MB!')
|
||
// return false
|
||
// }
|
||
return true
|
||
}
|
||
|
||
|
||
// 表单提交
|
||
const onSubmit = () => {
|
||
if (!formRef.value) return;
|
||
|
||
formRef.value.validate(async (valid) => {
|
||
if (!valid) return;
|
||
try {
|
||
const payload = {
|
||
...formData.value,
|
||
gender: formData.value.gender !== undefined && formData.value.gender !== null ? Number(formData.value.gender) : '',
|
||
mobile: formData.value.mobile,
|
||
};
|
||
const res = formData.value.id ? await appuserAthleteUpdatePut(payload) : await appuserAthleteAddPost(payload);
|
||
if (res.succeeded) {
|
||
ElMessage.success(dialogTitle.value + '成功');
|
||
isShowDialog.value = false;
|
||
initParam.value.refresh = !initParam.value.refresh;
|
||
// 清理计时器
|
||
if (sendInterval.value) {
|
||
clearInterval(sendInterval.value);
|
||
sendInterval.value = null;
|
||
}
|
||
sending.value = false;
|
||
sendTimer.value = 0;
|
||
} else {
|
||
ElMessage.error(res.errors || res.msg || dialogTitle.value + '失败');
|
||
}
|
||
} catch (error) {
|
||
ElMessage.error(dialogTitle.value + '失败');
|
||
console.error(error);
|
||
}
|
||
});
|
||
};
|
||
|
||
// 取消
|
||
const onCancel = () => {
|
||
// 清理计时器
|
||
if (sendInterval.value) {
|
||
clearInterval(sendInterval.value);
|
||
sendInterval.value = null;
|
||
}
|
||
sending.value = false;
|
||
sendTimer.value = 0;
|
||
isShowDialog.value = false;
|
||
};
|
||
</script>
|
||
<style scoped lang="scss">
|
||
.tabs {
|
||
height: 100%;
|
||
}
|
||
|
||
.dialog-footer {
|
||
margin-top: 80px;
|
||
}
|
||
|
||
/* 时长组合框样式 */
|
||
.duration-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.duration-row .el-input {
|
||
width: 120px;
|
||
}
|
||
|
||
.duration-separator {
|
||
color: #606266;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.duration-label {
|
||
color: #909399;
|
||
font-size: 14px;
|
||
}
|
||
</style>
|
||
|
||
<style>
|
||
.avatar-uploader .el-upload {
|
||
border: 1px dashed var(--el-border-color);
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
position: relative;
|
||
overflow: hidden;
|
||
transition: var(--el-transition-duration-fast);
|
||
}
|
||
|
||
.avatar-uploader .el-upload:hover {
|
||
border-color: var(--el-color-primary);
|
||
}
|
||
|
||
.el-icon.avatar-uploader-icon {
|
||
font-size: 28px;
|
||
color: #8c939d;
|
||
width: 178px;
|
||
height: 178px;
|
||
text-align: center;
|
||
}
|
||
</style>
|
||
|
||
<style>
|
||
#pane-first {
|
||
height: 100%;
|
||
}
|
||
|
||
#pane-second {
|
||
height: 100%;
|
||
}
|
||
</style> |