update:报名bug修复

This commit is contained in:
ltx 2026-03-26 19:46:08 +08:00
parent 851df6d408
commit 43aaeedc31
4 changed files with 361 additions and 57 deletions

2
.env
View File

@ -8,4 +8,4 @@ VITE_OPEN = true
VITE_OPEN_CDN = false
# public path 配置线上环境路径(打包)、本地通过 http-server 访问时,请置空即可
VITE_PUBLIC_PATH = 'https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/event_web/202603250222/'
VITE_PUBLIC_PATH = 'https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/event_web/202603261000/'

View File

@ -5,52 +5,114 @@
<template #header>
<div style="display: flex; justify-content: space-between; align-items: center">
<span><span style="color: red;">*</span> 项目配置</span>
<el-button type="primary" plain icon="ele-Plus" @click="addProject">选择项目</el-button>
<el-button type="primary" plain @click="addProject">选择项目</el-button>
</div>
</template>
<el-collapse>
<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
}}</el-descriptions-item>
<el-descriptions-item label="费用">{{ item.fee }}</el-descriptions-item>
<el-descriptions-item :label="`组别${idx + 1}:`" v-for="(group, idx) in item.groups">
<el-tag>{{ group.groupName }}</el-tag>
<span style="margin: 0 10px;">{{ group.birthDateStart }} -- {{ group.birthDateEnd
}}</span>
<span>{{ genderList[group.gender] }}</span>
</el-descriptions-item>
</el-descriptions>
</el-collapse-item>
</el-collapse>
<div class="project-card" v-for="(item, index) in localFormData.projects"
:key="item.projectId ?? item.id ?? index">
<div class="project-card__meta">
<div class="project-card__meta-name">{{ item.name }}</div>
<div class="project-card__meta-line">类型:{{ item.projectTypeName }}</div>
<div class="project-card__meta-line">时长: {{ item.durationMinutes ?? 0 }}分 {{ item.durationSeconds ?? 0 }}秒</div>
<div class="project-card__meta-line meta-muted">费用:{{ item.fee }} 元</div>
</div>
<div class="project-card__groups">
<div v-for="(group, gIndex) in (item.groups ?? [])"
:key="group.projectGroupId ?? group.id ?? gIndex" class="group-row">
<div class="group-row__fields">
<el-input v-model="group.groupName" class="group-row__name" placeholder="组别名称" />
<el-date-picker class="group-row__range" type="daterange" range-separator="~"
start-placeholder="开始日期" end-placeholder="结束日期"
:model-value="[group.birthDateStart, group.birthDateEnd]"
@update:modelValue="(v) => { group.birthDateStart = v?.[0]; group.birthDateEnd = v?.[1]; }" />
<el-select v-model="group.gender" class="group-row__gender" placeholder="性别">
<el-option label="全部" :value="0" />
<el-option label="男" :value="1" />
<el-option label="女" :value="2" />
</el-select>
</div>
<div class="group-row__actions">
<el-popconfirm title="确认删除该组别吗?" @confirm="removeGroup(index, gIndex)">
<template #reference>
<el-button text type="danger" icon="ele-Delete">删除</el-button>
</template>
</el-popconfirm>
</div>
</div>
<div v-if="!(item.groups ?? []).length" class="group-empty">暂无组别</div>
</div>
</div>
</el-card>
<el-card shadow="never">
<template #header>限报配置</template>
<el-row :gutter="12">
<el-col :span="12">
<el-form-item label="个人项目限报数" prop="registrationLimit.individualProjectLimit">
<el-input-number v-model="localFormData.registrationLimit.individualProjectLimit" :min="0"
:step="1" />
<div v-if="localFormData.projects?.length" class="project-limit-section">
<div class="project-limit-section__header">
<div class="project-limit-section__title">
<span class="required-star">*</span>
项目限报配置
</div>
<div class="project-limit-section__desc">为每个项目设置单独的限报数(必填)</div>
</div>
<div class="project-limit-table">
<el-table :data="localFormData.projects" stripe style="width: 100%">
<el-table-column label="项目" min-width="220">
<template #default="{ row, $index }">
<div class="project-limit-name">
<div class="project-limit-name__idx">#{{ $index + 1 }}</div>
<div class="project-limit-name__text">{{ row.name }}</div>
</div>
</template>
</el-table-column>
<el-table-column label="限报数" width="220" align="center">
<template #default="{ row, $index }">
<el-form-item
:prop="`projects.${$index}.limitCount`"
:rules="[{ required: true, message: '请填写项目限报数', trigger: 'change' }]"
style="margin: 0; justify-content: center;"
>
<el-input-number v-model="row.limitCount" :min="0" :step="1" :precision="0" :controls="true" class="project-limit-input" />
</el-form-item>
</template>
</el-table-column>
</el-table>
</div>
</div>
<div class="limit-summary">
<div class="limit-summary__card">
<div class="limit-summary__label">
<span class="required-star">*</span>
个人项目限报数
</div>
<el-form-item
prop="registrationLimit.individualProjectLimit"
:rules="[{ required: true, message: '请填写个人项目限报数', trigger: 'change' }]"
style="margin: 0"
>
<el-input-number v-model="localFormData.registrationLimit.individualProjectLimit" :min="0" :step="1" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="团队项目限报数" prop="registrationLimit.teamProjectLimit">
<el-input-number v-model="localFormData.registrationLimit.teamProjectLimit" :min="0"
:step="1" />
</div>
<div class="limit-summary__card">
<div class="limit-summary__label">
<span class="required-star">*</span>
团队项目限报数
</div>
<el-form-item
prop="registrationLimit.teamProjectLimit"
:rules="[{ required: true, message: '请填写团队项目限报数', trigger: 'change' }]"
style="margin: 0"
>
<el-input-number v-model="localFormData.registrationLimit.teamProjectLimit" :min="0" :step="1" />
</el-form-item>
</el-col>
</el-row>
</div>
</div>
</el-card>
</el-form>
@ -167,6 +229,13 @@ const columns = ref([
const removeProject = (idx) => localFormData.value.projects.splice(idx, 1);
const removeGroup = (projectIndex, groupIndex) => {
const p = localFormData.value.projects?.[projectIndex];
if (!p) return;
if (!Array.isArray(p.groups)) p.groups = [];
p.groups.splice(groupIndex, 1);
ElMessage.success('删除组别成功');
};
const isSyncingFromParent = ref(false);
@ -241,23 +310,172 @@ defineExpose({ validate });
margin-bottom: 24px;
}
.project-collapse-title {
.project-limit-section {
margin-bottom: 16px;
}
.project-limit-section__header {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 12px;
margin-bottom: 10px;
}
.project-limit-section__title {
font-weight: 600;
color: var(--el-text-color-primary);
}
.project-limit-section__desc {
color: var(--el-text-color-secondary);
font-size: 12px;
white-space: nowrap;
}
.required-star {
color: var(--el-color-danger);
margin-right: 4px;
}
.project-limit-table {
border: 1px solid var(--el-border-color);
border-radius: 12px;
overflow: hidden;
background: #fff;
}
.project-limit-name {
display: flex;
align-items: center;
gap: 10px;
}
.project-collapse-title__label {
color: var(--el-text-color-secondary);
.project-limit-name__idx {
font-size: 12px;
white-space: nowrap;
margin-left: 40px;
color: var(--el-text-color-secondary);
background: var(--el-fill-color-light);
border: 1px solid var(--el-border-color-lighter);
padding: 2px 8px;
border-radius: 999px;
}
.project-collapse-title .el-input-number {
.project-limit-name__text {
font-weight: 600;
color: var(--el-text-color-primary);
}
.project-limit-input {
width: 160px;
}
.limit-summary {
margin-top: 14px;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
.limit-summary__card {
border: 1px solid var(--el-border-color);
border-radius: 12px;
background: var(--el-fill-color-light);
padding: 12px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.limit-summary__label {
font-weight: 600;
color: var(--el-text-color-primary);
}
.project-card {
display: flex;
border: 1px solid var(--el-border-color);
border-radius: 14px;
overflow: hidden;
background: #fff;
margin-bottom: 16px;
}
.project-card__meta {
width: 210px;
padding: 16px 14px;
background: linear-gradient(180deg, #4f8ffb 0%, #2f73f5 100%);
color: #fff;
display: flex;
flex-direction: column;
gap: 8px;
}
.project-card__meta-name {
font-size: 16px;
font-weight: 700;
line-height: 1.2;
}
.project-card__meta-line {
font-size: 13px;
opacity: 0.95;
}
.meta-muted {
opacity: 0.85;
}
.project-card__groups {
flex: 1;
padding: 14px 16px;
display: flex;
flex-direction: column;
gap: 12px;
background: #fff;
}
.group-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 10px 12px;
border: 1px solid var(--el-border-color-lighter);
border-radius: 10px;
background: var(--el-fill-color-light);
}
.group-row__fields {
display: grid;
grid-template-columns: 140px minmax(280px, 1fr) 120px;
gap: 10px;
align-items: center;
flex: 1;
}
.group-row__name,
.group-row__range,
.group-row__gender {
width: 100%;
}
.group-row__actions {
opacity: 0;
transition: opacity 0.15s ease;
flex: none;
}
.group-row:hover .group-row__actions {
opacity: 1;
}
.group-empty {
color: var(--el-text-color-secondary);
font-size: 13px;
padding: 6px 2px;
}
.input-hint {
font-size: 12px;
color: #909399;

View File

@ -16,11 +16,36 @@
<el-divider content-position="center" style="margin: 60px 0 40px;">账户信息</el-divider>
<el-form-item label="收款商户" prop="merchantId" v-if="localFormData.chargeMode !== 0">
<el-select v-model="localFormData.merchantId" filterable placeholder="请选择商户(可选)" style="width: 100%">
<el-option v-for="m in merchantOptions" :key="m.value" :label="m.label" :value="m.value" />
</el-select>
</el-form-item>
<div v-if="localFormData.chargeMode !== 0" class="account-panel">
<el-form-item label="收款商户" prop="merchantId" required style="margin-bottom: 14px;">
<el-select v-model="localFormData.merchantId" filterable placeholder="请选择商户" @change="syncMerchantInfo" style="width: 100%">
<el-option v-for="m in merchantOptions" :key="m.value" :label="m.label" :value="m.value" />
</el-select>
</el-form-item>
<div class="account-grid">
<div class="account-item">
<div class="account-item__label">商户名称</div>
<el-input v-model="localFormData.merchantName" disabled placeholder="-" />
</div>
<div class="account-item">
<div class="account-item__label">统一信用代码</div>
<el-input v-model="localFormData.creditCode" disabled placeholder="-" />
</div>
<div class="account-item">
<div class="account-item__label">开户银行</div>
<el-input v-model="localFormData.bankName" disabled placeholder="-" />
</div>
<div class="account-item">
<div class="account-item__label">银行账号</div>
<el-input v-model="localFormData.accountNumber" disabled placeholder="-" />
</div>
<div class="account-item">
<div class="account-item__label">营业执照</div>
<img :src="businessLicenseUrl" style="width: 100px; height: 100px;" />
</div>
</div>
</div>
<!-- <el-row :gutter="12">
<el-col :span="12">
<el-form-item label="开户银行" prop="bankName">
@ -48,6 +73,8 @@ const localFormData = ref({
chargeMode: 0,
price: 0,
merchantId: undefined,
merchantName: '',
creditCode: '',
bankName: '',
accountNumber: '',
});
@ -68,21 +95,52 @@ watch(
(v) => emit('update:modelValue', { ...props.modelValue, ...v }),
{ deep: true }
);
const businessLicenseUrl = ref('');
const merchantOptions = ref([]);
onMounted(async () => {
try {
const res = await merchantPageGet({ PageNo: 1, PageSize: 200 });
const rows = res?.data?.rows ?? [];
merchantOptions.value = rows.map((m) => ({ label: m.merchantName ?? m.name ?? `${m.id}`, value: m.id }));
merchantOptions.value = rows.map((m) => ({
label: m.merchantName ?? m.name ?? `${m.id}`,
value: m.id,
merchantName: m.merchantName ?? m.name ?? '',
creditCode: m.creditCode ?? '',
bankName: m.bankName ?? '',
accountNumber: m.accountNumber ?? '',
businessLicenseUrl: m.businessLicenseUrl ?? '',
}));
} catch {
merchantOptions.value = [];
}
syncMerchantInfo();
});
const syncMerchantInfo = () => {
const id = localFormData.value.merchantId;
if (!id) {
localFormData.value.merchantName = '';
localFormData.value.creditCode = '';
localFormData.value.bankName = '';
localFormData.value.accountNumber = '';
return;
}
// el-select 可能返回 string/number,这里做宽松匹配避免找不到
const m = (merchantOptions.value ?? []).find((x) => x?.value == id);
localFormData.value.merchantName = m?.merchantName ?? '';
localFormData.value.creditCode = m?.creditCode ?? '';
localFormData.value.bankName = m?.bankName ?? '';
localFormData.value.accountNumber = m?.accountNumber ?? '';
businessLicenseUrl.value = m?.businessLicenseUrl ?? '';
};
// watch(() => localFormData.value.merchantId, syncMerchantInfo, { immediate: true , deep: true});
watch(merchantOptions, syncMerchantInfo, { deep: true });
const rules = {
chargeMode: [{ required: true, message: '请选择收费模式', trigger: 'change' }],
price: [{ required: false, message: '请输入价格', trigger: 'change' }],
merchantId: [{ required: true, message: '请选择收款商户', trigger: 'change' }],
bankName: [{ required: false, message: '请输入开户银行', trigger: 'blur' }],
accountNumber: [{ required: false, message: '请输入银行账号', trigger: 'blur' }],
};
@ -104,6 +162,33 @@ defineExpose({ validate });
margin-bottom: 24px;
}
.account-panel {
margin-top: 10px;
}
.account-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
padding: 12px;
border: 1px solid var(--el-border-color);
border-radius: 12px;
background: var(--el-fill-color-light);
}
.account-item {
padding: 10px 12px;
border-radius: 10px;
background: #fff;
border: 1px solid var(--el-border-color-lighter);
}
.account-item__label {
font-size: 12px;
color: var(--el-text-color-secondary);
margin-bottom: 6px;
}
.fee-type-group {
display: flex;
width: 100%;

View File

@ -266,10 +266,16 @@ const handleSubmit = async () => {
const rawProjects = formData.value.projectConfig?.projects ?? [];
const projects = rawProjects.map((p) => {
// 后端字段要求:projects[].projectId,而不是 projects[].id
const { id, projectId: existingProjectId, ...rest } = p ?? {};
const { id, projectId: existingProjectId, groups, ...rest } = p ?? {};
const mappedGroups = (groups ?? []).map((g) => {
// 后端字段要求:groups[].projectGroupId,而不是 groups[].id
const { id: groupId, projectGroupId: existingGroupId, ...gRest } = g ?? {};
return { ...gRest, projectGroupId: existingGroupId ?? groupId };
});
return {
...rest,
projectId: existingProjectId ?? id,
groups: mappedGroups,
};
});
const payload = {
@ -369,11 +375,6 @@ defineExpose({
margin: 0 auto;
}
.step-form-card {
/* border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15); */
}
.card-header {
text-align: center;
}