update:bug修复

This commit is contained in:
ltx 2026-03-25 18:25:26 +08:00
parent 4f89998c92
commit 9557753f92
12 changed files with 163 additions and 73 deletions

View File

@ -173,7 +173,7 @@ export const appuserAthleteUpdatePut = (data) => {
* @param {string} data.imageUrl - 身份证图片URL
*/
export const appuserAthleteOcrPost = (data) => {
return http.post('/appuser/member/ocr', data);
return http.post('/appuser/member/ocr', { imageUrl: data});
};
/**

View File

@ -24,9 +24,9 @@
<el-form-item label="报名方式" prop="registrationType">
<el-radio-group v-model="localFormData.registrationType">
<el-radio label="OnlyIndividual">只允许个人报名</el-radio>
<el-radio label="OnlyTeam">只允许团队报名</el-radio>
<el-radio label="Both">两者都有</el-radio>
<el-radio :label="0">只允许个人报名</el-radio>
<el-radio :label="1">只允许团队报名</el-radio>
<el-radio :label="2">两者都有</el-radio>
</el-radio-group>
</el-form-item>
@ -36,6 +36,7 @@
</el-select>
</el-form-item>
<!-- 报名起止时间 -->
<el-form-item label="报名起止时间" prop="registrationTime">
<el-date-picker v-model="localFormData.registrationTime" type="datetimerange" range-separator="至"
start-placeholder="开始" end-placeholder="结束" />
@ -193,6 +194,18 @@ const rules = ref({
pointsRuleId: [{ required: true, message: '请选择积分规则', trigger: 'change' }],
registrationTime: [{ type: 'array', required: true, message: '请选择报名起止时间', trigger: 'change' }],
matchTime: [{ type: 'array', required: true, message: '请选择比赛起止时间', trigger: 'change' }],
materialUploadTime: [
{
validator: (rule, value, callback) => {
// 只有选择“需要上传素材”时,才要求填写上传起止时间
if (!localFormData.value.isNeedMaterial) return callback();
const ok = Array.isArray(value) && value.length === 2 && value[0] && value[1];
if (!ok) return callback(new Error('请选择上传素材起止时间'));
callback();
},
trigger: 'change',
},
],
regionIds: [{ type: 'array', required: true, message: '请选择省/市/区', trigger: 'change' }],
detailAddress: [{ required: true, message: '请输入详细地址', trigger: 'blur' }],
eventIntroduction: [{ required: true, message: '请输入赛事简介', trigger: 'blur' }],

View File

@ -10,9 +10,10 @@
<template #default="{ row, $index }">
<el-form-item :prop="`units.${$index}.unitType`" :rules="[{ required: true, message: '请选择单位类型', trigger: 'change' }]" style="margin: 0;width: 100%;">
<el-select v-model="row.unitType" placeholder="请选择" style="width: 100%">
<el-option label="主办方" :value="0" />
<el-option label="承办方" :value="1" />
<el-option label="协办方" :value="2" />
<el-option label="主办单位" :value="0" />
<el-option label="承办单位" :value="1" />
<el-option label="协办单位" :value="2" />
<el-option label="赞助单位" :value="3" />
</el-select>
</el-form-item>
</template>

View File

@ -9,7 +9,15 @@
</div>
</template>
<el-collapse>
<el-collapse-item :title="`项目${index + 1}:${item.name}`" v-for="(item, index) in localFormData.projects">
<el-collapse-item v-for="(item, index) in localFormData.projects" :key="item.projectId ?? item.id ?? index">
<template #title>
<div class="project-collapse-title">
<span>项目{{ index + 1 }}:{{ item.name }}</span>
<span class="project-collapse-title__label">限报数</span>
<el-input-number v-model="item.limitCount" :min="0" :step="1" :precision="0" :controls="true"
@click.stop />
</div>
</template>
<el-descriptions>
<el-descriptions-item label="类型">{{ item.projectTypeName }}</el-descriptions-item>
<el-descriptions-item label="时长">{{ item.durationMinutes }}:{{ item.durationSeconds
@ -71,7 +79,7 @@
</template>
<script setup>
import { ref, watch } from 'vue';
import { ref, watch, nextTick } from 'vue';
import ProTable from '/@/components/ProTable/index.vue';
import { projectPageGet, projectDetailGet } from '/@/api/match/project';
import { ElMessage } from 'element-plus';
@ -95,18 +103,28 @@ const selectionChange = (list) => {
timeProjects.value = list
}
const onSubmit = () => {
// localFormData.value.projects = timeProjects.value;
localFormData.value.projects = []
if (timeProjects.value.length > 0) {
timeProjects.value.forEach(async (e) => {
const res = await projectDetailGet({ id: e.id })
localFormData.value.projects.push(res.data)
console.log(localFormData.value.projects)
})
const onSubmit = async () => {
// 一次性拉取详情,避免 forEach(async) 导致多次触发更新/竞态
localFormData.value.projects = [];
const selected = timeProjects.value ?? [];
if (!selected.length) {
isShowDialog.value = false;
return;
}
const projects = await Promise.all(
selected.map(async (e) => {
const res = await projectDetailGet({ id: e.id });
return {
...(res.data ?? {}),
limitCount: res.data?.limitCount ?? 0,
};
})
);
localFormData.value.projects = projects;
isShowDialog.value = false;
}
};
const isShowDialog = ref(false);
@ -150,9 +168,17 @@ const columns = ref([
const removeProject = (idx) => localFormData.value.projects.splice(idx, 1);
const normalizeFromParent = (v) => {
const isSyncingFromParent = ref(false);
const normalizeFromParent = async (v) => {
isSyncingFromParent.value = true;
localFormData.value.registrationLimit = v?.registrationLimit ?? { individualProjectLimit: 1, teamProjectLimit: 1 };
localFormData.value.projects = v.projects
localFormData.value.projects = (v?.projects ?? []).map((p) => ({
...p,
limitCount: p?.limitCount ?? 0,
}));
await nextTick();
isSyncingFromParent.value = false;
// localFormData.value.projects = (v?.projects ?? []).map((p) => ({
// ...p,
// // 用文本编辑组别,提交时再解析成 groups[]
@ -165,13 +191,15 @@ const normalizeFromParent = (v) => {
watch(
() => props.modelValue,
(v) => normalizeFromParent(v),
async (v) => await normalizeFromParent(v),
{ deep: true, immediate: true }
);
watch(
localFormData,
(v) => {
// 防止父子双向同步时形成递归更新
if (isSyncingFromParent.value) return;
// const projects = v.projects.map((p) => ({
// id: p.id,
// name: p.name,
@ -213,6 +241,23 @@ defineExpose({ validate });
margin-bottom: 24px;
}
.project-collapse-title {
display: flex;
align-items: center;
gap: 10px;
}
.project-collapse-title__label {
color: var(--el-text-color-secondary);
font-size: 12px;
white-space: nowrap;
margin-left: 40px;
}
.project-collapse-title .el-input-number {
width: 160px;
}
.input-hint {
font-size: 12px;
color: #909399;

View File

@ -263,6 +263,15 @@ const handleSubmit = async () => {
if (valid) {
submitting.value = true;
const b = formData.value.basicInfo;
const rawProjects = formData.value.projectConfig?.projects ?? [];
const projects = rawProjects.map((p) => {
// 后端字段要求:projects[].projectId,而不是 projects[].id
const { id, projectId: existingProjectId, ...rest } = p ?? {};
return {
...rest,
projectId: existingProjectId ?? id,
};
});
const payload = {
id: b.id,
eventName: b.eventName,
@ -286,7 +295,7 @@ const handleSubmit = async () => {
eventIntroduction: b.eventIntroduction,
customConfigJson: b.customConfigJson,
units: formData.value.units?.units ?? [],
projects: formData.value.projectConfig?.projects ?? [],
projects,
registrationLimit: formData.value.projectConfig?.registrationLimit,
feeConfig: formData.value.feeConfig,
};
@ -307,7 +316,7 @@ const handleSubmit = async () => {
// 重置表单
const handleReset = () => {
successDialogVisible.value = false;
dialog.value.isShowDialog = false;
// dialog.value.isShowDialog = false;
// 重置所有表单数据
formData.value.basicInfo = {
@ -334,7 +343,7 @@ const handleReset = () => {
registrationLimit: { individualProjectLimit: 1, teamProjectLimit: 1 },
};
formData.value.feeConfig = {
chargeMode: 'Free',
chargeMode: 0,
price: 0,
merchantId: undefined,
merchantName: '',

View File

@ -140,7 +140,7 @@ const columns = reactive([
search: {
el: 'date-picker',
props: {
type: 'daterange',
type: 'date',
},
},
},
@ -151,7 +151,7 @@ const columns = reactive([
search: {
el: 'date-picker',
props: {
type: 'daterange',
type: 'date',
},
},
},
@ -162,7 +162,7 @@ const columns = reactive([
search: {
el: 'date-picker',
props: {
type: 'daterange',
type: 'date',
},
},
},
@ -173,7 +173,7 @@ const columns = reactive([
search: {
el: 'date-picker',
props: {
type: 'daterange',
type: 'date',
},
},
},

View File

@ -230,8 +230,8 @@ const columns = reactive([
el: 'select',
},
enum: [
{ label: '个人报名', value: 1 },
{ label: '团队报名', value: 2 },
{ label: '个人报名', value: 0 },
{ label: '团队报名', value: 1 },
],
},
{
@ -239,7 +239,10 @@ const columns = reactive([
prop: 'unitName',
label: '单位名称',
},
{
prop: 'totalPersonCount',
label: '总人数',
},
{ prop: 'contactName', label: '报名用户', width: '120px', },
{ prop: 'contactPhone', label: '联系电话', width: '120px', search: { el: 'input' }, },
{

View File

@ -83,9 +83,9 @@
<el-date-picker v-model="group.birthDateStart" type="date" placeholder="出生日期开始" style="margin-right: 18px"></el-date-picker>
<el-date-picker v-model="group.birthDateEnd" type="date" placeholder="出生日期结束" style="margin-right: 18px"></el-date-picker>
<el-select v-model="group.gender" placeholder="性别" style="width: 100px">
<el-option label="男" :value="1"></el-option>
<el-option label="女" :value="2"></el-option>
<el-option label="不限" :value="0"></el-option>
<el-option label="男" :value="0"></el-option>
<el-option label="女" :value="1"></el-option>
<el-option label="不限" value=""></el-option>
</el-select>
<el-button
circle

View File

@ -98,8 +98,8 @@ const columns = ref([
},
enum: [
{ label: '全部', value: '' },
{ label: '男', value: 1 },
{ label: '女', value: 2 },
{ label: '男', value: 0 },
{ label: '女', value: 1 },
],
},
{ prop: 'mobile', label: '联系电话', search: { el: 'input' } },

View File

@ -38,6 +38,12 @@
<template #idCardImageUrl="scope">
<el-image :src="scope.row.idCardImageUrl" style="width: 100px; " fit="cover" />
</template>
<template #auditStatus="scope">
<el-tag :type="statusList[scope.row.auditStatus]">
{{ scope.row.auditStatus == 1 ? '待审核' : scope.row.auditStatus == 2 ? '审核通过' : scope.row.auditStatus == 3 ? '审核不通过' : '' }}
</el-tag>
</template>
<template #action="scope">
<el-button icon="ele-Edit" size="small" text type="primary" @click="onOpenMenu(scope.row)">
编辑 </el-button>
@ -54,14 +60,15 @@
</el-tabs>
<!-- 团队新增弹窗 -->
<el-dialog title="新增团队" v-model="isShowTeamDialog" width="800px">
<el-dialog :title="teamFormData.id ? '编辑团队' : '新增团队'" v-model="isShowTeamDialog" width="800px">
<el-form ref="teamFormRef" :model="teamFormData" :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="队员">
<el-button type="primary" @click="openMemberDialog">添加队员</el-button>
<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>
@ -70,6 +77,7 @@
<span class="muted">未选择队员</span>
</template>
</div>
</div>
</el-form-item>
<el-form-item label="领队" prop="leaderIds">
@ -94,7 +102,7 @@
<!-- 成员选择弹窗 -->
<el-dialog title="选择队员" v-model="isShowMemberDialog" width="900px">
<ProTable ref="memberTableRef" :request-api="appuserAthletePageGet" :columns="columnsMember" :pagination="true" />
<ProTable ref="memberTableRef" :request-api="appuserAthletePageGet" :columns="columnsMember" :initParam="initParam" :pagination="true" />
<template #footer>
<span class="dialog-footer">
<el-button @click="cancelMemberSelection">取消</el-button>
@ -112,7 +120,7 @@
<el-form-item label="运动员身份证照片" 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" />
<img v-if="formData.idCardImageUrl" :src="formData.idCardImageUrl" class="avatar" style="height: 150px;" />
<el-icon v-else class="avatar-uploader-icon">
<Plus />
</el-icon>
@ -122,28 +130,26 @@
<!-- 教练姓名 -->
<el-form-item label="姓名" prop="name">
<el-input v-model="formData.name" placeholder="请输入教练姓名"></el-input>
<el-input v-model="formData.name" disabled placeholder="请输入教练姓名"></el-input>
</el-form-item>
<!-- 性别 -->
<el-form-item label="性别" prop="gender">
<el-select v-model="formData.gender" placeholder="请选择性别">
<el-option label="男" value="1"></el-option>
<el-option label="女" value="2"></el-option>
</el-select>
<el-form-item label="性别" prop="gender">
<el-tag :type="formData.gender === 0 ? '' : formData.gender === 1 ? 'success' : 'danger'">
{{ formData.gender === 0 ? '男' : formData.gender === 1 ? '女' : '保密' }}</el-tag>
</el-form-item>
<el-form-item label="出生日期" prop="birthDate">
<el-date-picker v-model="formData.birthDate" placeholder="请选择出生日期" />
<el-form-item label="出生日期" prop="birthDate">
<el-input v-model="formData.birthDate" placeholder="请选择出生日期" disabled></el-input>
</el-form-item>
<el-form-item label="身份证号" prop="idCard">
<el-input v-model="formData.idCard" placeholder="请输入身份证号"></el-input>
<el-form-item label="身份证号" prop="idCardNumber">
<el-input v-model="formData.idCardNumber" disabled placeholder="请输入身份证号"></el-input>
</el-form-item>
<!-- 联系电话 -->
<el-form-item label="联系电话" prop="phone">
<el-input v-model="formData.phone" placeholder="请输入联系电话" class="fee-input">
<el-form-item label="联系电话" prop="mobile">
<el-input v-model="formData.mobile" placeholder="请输入联系电话" class="fee-input">
<!-- <template #append>
<el-button type="primary" @click="onSendCode" >
{{ isSendCode ? '已发送' : '发送验证码' }}
@ -177,13 +183,17 @@ const activeTab = ref('first')
const getDefaultFormData = () => ({
id: undefined,
name: '',
idCard: '',
idCardNumber: '',
gender: '',
idCardImageUrl: '',
birthDate: '',
phone: '',
mobile: '',
});
const statusList = ref({
1: 'info',
2: 'success',
3: 'danger',
})
const formData = ref(getDefaultFormData())
const initParam = ref({
@ -195,18 +205,21 @@ const formRules = ref({
name: [
{ required: true, message: '请输入运动员姓名', trigger: 'blur' }
],
idCard: [
idCardNumber: [
{ required: true, message: '请输入身份证号', trigger: 'change' }
],
gender: [
{ required: true, message: '请选择性别', trigger: 'change' }
],
phone: [
mobile: [
{ required: true, message: '请输入联系电话', trigger: 'blur' }
],
idCardImageUrl: [
{ required: true, message: '请上传身份证照片', trigger: 'change' }
],
birthDate: [
{ required: true, message: '请选择出生日期', trigger: 'change' }
],
});
const columns = ref([
@ -231,9 +244,11 @@ const columnsSport = ref([
{ label: '女', value: 1 },
],
},
{ prop: 'idCardImageUrl', label: '身份证' },
// { prop: 'idCardImageUrl', label: '身份证' },
{ prop: 'idCardNumber', label: '身份证号' },
{ prop: 'auditStatus', label: '审核状态' },
{ prop: 'phone', label: '联系电话', search: { el: 'input' } },
{ prop: 'mobile', label: '联系电话', search: { el: 'input' } },
{ prop: 'action', label: '操作', align: 'center', fixed: 'right' },
]);
@ -265,8 +280,8 @@ const memberTableRef = ref(null);
const columnsMember = ref([
{ type: 'selection' },
{ prop: 'name', label: '姓名', align: 'center' },
{ prop: 'idCard', label: '身份证号', align: 'center' },
{ prop: 'phone', label: '联系电话', align: 'center' },
{ prop: 'idCardNumber', label: '身份证号', align: 'center' },
{ prop: 'mobile', label: '联系电话', align: 'center' },
]);
// 领队/教练下拉选项
@ -408,6 +423,10 @@ const cancelMemberSelection = () => {
// 提交新增团队
const onTeamSubmit = () => {
if (!teamFormRef.value) return;
if(!teamFormData.value.name) return ElMessage.error('请输入团队名称');
if(!teamFormData.value.memberIds.length) return ElMessage.error('请选择队员');
if(!teamFormData.value.leaderIds.length) return ElMessage.error('请选择领队');
if(!teamFormData.value.coachIds.length) return ElMessage.error('请选择教练');
teamFormRef.value.validate(async (valid) => {
if (!valid) return;
try {
@ -419,7 +438,7 @@ const onTeamSubmit = () => {
};
const res = teamFormData.value.id ? await teamUpdatePut({ id: teamFormData.value.id, ...payload }) : await teamAddPost(payload);
if (res && res.succeeded) {
ElMessage.success('新增团队成功');
ElMessage.success(teamFormData.value.id ? '更新团队成功' : '新增团队成功');
isShowTeamDialog.value = false;
// 刷新团队表格(ProTable 暴露的 getTableList)
try {
@ -448,11 +467,11 @@ const onOpenMenu = (row) => {
...getDefaultFormData(),
id: row.id,
name: row.name ?? '',
idCard: row.idCard ?? '',
idCardNumber: row.idCardNumber ?? '',
gender: row.gender !== undefined && row.gender !== null ? String(row.gender) : '',
idCardImageUrl: row.idCardImageUrl ?? '',
birthDate: row.birthDate ?? '',
phone: row.phone ?? row.phone ?? '',
mobile: row.mobile ?? row.mobile ?? '',
};
}
isShowDialog.value = true;
@ -515,10 +534,10 @@ const handleAvatarSuccess = async (response,
const res = await appuserAthleteOcrPost(response[0].url)
if(res.succeeded){
formData.value.name = res.data.name
formData.value.idCard = res.data.idCard
formData.value.gender = res.data.gender !== undefined && res.data.gender !== null ? String(res.data.gender) : ''
formData.value.birthDate = res.data.birthDate
formData.value.idCardNumber = res.data.idCardNumber
formData.value.gender = res.data.gender == '男' ? 0 : 1
formData.value.idCardImageUrl = response[0].url
formData.value.birthDate = res.data.birthDate
}else{
ElMessage.error('请上传正确的身份证/护照')
}
@ -547,7 +566,7 @@ const onSubmit = () => {
const payload = {
...formData.value,
gender: formData.value.gender !== undefined && formData.value.gender !== null ? Number(formData.value.gender) : '',
phone: formData.value.phone,
mobile: formData.value.mobile,
};
const res = formData.value.id ? await appuserAthleteUpdatePut(payload) : await appuserAthleteAddPost(payload);
if (res.succeeded) {

View File

@ -138,8 +138,8 @@ const columns = ref([
},
enum: [
{ label: '全部', value: '' },
{ label: '男', value: 1 },
{ label: '女', value: 0 },
{ label: '男', value: 0 },
{ label: '女', value: 1 },
],
},
{ prop: 'mobile', label: '联系电话' , search: { el: 'input' }},

View File

@ -138,8 +138,8 @@ const columns = ref([
},
enum: [
{ label: '全部', value: '' },
{ label: '男', value: 1 },
{ label: '女', value: 0 },
{ label: '男', value: 0 },
{ label: '女', value: 1 },
],
},
{ prop: 'mobile', label: '联系电话' , search: { el: 'input' }},