赛事详情

This commit is contained in:
马梓彭 2026-04-27 11:23:40 +08:00
parent c3f8796c88
commit ecec637f05
3 changed files with 256 additions and 23 deletions

View File

@ -44,24 +44,33 @@
</el-form-item>
<el-form-item label="赛事类型" prop="eventType">
<el-select v-model="localFormData.eventType" placeholder="请选择赛事类型" style="width: 100%" :disabled="readonly">
<el-select v-if="!readonly" v-model="localFormData.eventType" placeholder="请选择赛事类型" style="width: 100%">
<el-option label="线上" :value="0" />
<el-option label="线下" :value="1" />
</el-select>
<span v-else>
{{ localFormData.eventType === 0 ? '线上' : localFormData.eventType === 1 ? '线下' : '-' }}
</span>
</el-form-item>
<el-form-item label="报名方式" prop="registrationType">
<el-radio-group v-model="localFormData.registrationType" :disabled="readonly">
<el-radio-group v-if="!readonly" v-model="localFormData.registrationType" :disabled="readonly">
<el-radio :label="0">个人报名</el-radio>
<el-radio :label="1">团队报名</el-radio>
<el-radio :label="2">个人/团队报名</el-radio>
</el-radio-group>
<span v-else>
{{ localFormData.registrationType === 0 ? '个人报名' : localFormData.registrationType === 1 ? '团队报名' : localFormData.registrationType === 2 ? '个人/团队报名' : '-' }}
</span>
</el-form-item>
<el-form-item label="积分发放规则" prop="pointsRuleId">
<el-select v-model="localFormData.pointsRuleId" placeholder="请选择积分发放规则" filterable style="width: 100%" :disabled="readonly">
<el-select v-if="!readonly" v-model="localFormData.pointsRuleId" placeholder="请选择积分发放规则" filterable style="width: 100%">
<el-option v-for="r in pointsRuleOptions" :key="r.value" :label="r.label" :value="r.value" />
</el-select>
<span v-else>
{{ localFormData.pointsRuleName || '-' }}
</span>
</el-form-item>
<!-- 报名起止时间 -->
@ -89,19 +98,22 @@
</el-form-item>
<el-form-item label="赛事地址(省/市/区)" prop="regionIds">
<div style="display: flex; gap: 8px ;width: 90%;">
<div v-if="!readonly" style="display: flex; gap: 8px ;width: 90%;">
<el-select v-model="localFormData.regionIds[0]" placeholder="请选择省份" style="flex: 1"
@change="handleProvinceChange" clearable :disabled="readonly">
@change="handleProvinceChange" clearable>
<el-option v-for="p in provinces" :key="p.id" :label="p.name" :value="p.code" />
</el-select>
<el-select v-model="localFormData.regionIds[1]" placeholder="请选择城市" style="flex: 1"
@change="handleCityChange" clearable :disabled="readonly">
@change="handleCityChange" clearable>
<el-option v-for="c in cities" :key="c.id" :label="c.name" :value="c.code" />
</el-select>
<el-select v-model="localFormData.regionIds[2]" placeholder="请选择区县" style="flex: 1" clearable :disabled="readonly">
<el-select v-model="localFormData.regionIds[2]" placeholder="请选择区县" style="flex: 1" clearable>
<el-option v-for="d in districts" :key="d.id" :label="d.name" :value="d.code" />
</el-select>
</div>
<div v-else>
{{ [localFormData.provinceName, localFormData.cityName, localFormData.districtName].filter(x => x).join(' / ') || '-' }}
</div>
</el-form-item>
<el-form-item label="详细地址" prop="detailAddress">
@ -138,8 +150,12 @@ const localFormData = ref({
coverImage: '',
eventVideo: '',
eventType: undefined, // 0/1
registrationType: '', // OnlyIndividual/OnlyTeam/Both
registrationType: undefined, // 0/1/2
pointsRuleId: undefined,
pointsRuleName: '',
provinceName: '',
cityName: '',
districtName: '',
registrationTime: [],
isNeedMaterial: false,
materialRemark: '',
@ -162,10 +178,15 @@ watch(
if(k === 'regionIds' && Array.isArray(v[k])) {
// 确保 regionIds 是长度为 3 的数组
for(let i = 0; i < 3; i++) {
if (v[k][i] !== undefined) localFormData.value.regionIds[i] = v[k][i].toString();
if (v[k][i] !== undefined) localFormData.value.regionIds[i] = v[k][i] !== null ? v[k][i].toString() : undefined;
}
}
});
// 确保新字段也被赋值
if (v.pointsRuleName !== undefined) localFormData.value.pointsRuleName = v.pointsRuleName;
if (v.provinceName !== undefined) localFormData.value.provinceName = v.provinceName;
if (v.cityName !== undefined) localFormData.value.cityName = v.cityName;
if (v.districtName !== undefined) localFormData.value.districtName = v.districtName;
console.log('after init', localFormData.value);
// 如果有省份数据,重新加载对应的城市和区县数据

View File

@ -0,0 +1,192 @@
<template>
<el-form ref="formRef" :model="localFormData" :rules="rules" label-position="right" label-width="120px" class="step-fee">
<el-form-item label="收费模式" prop="chargeMode">
<el-radio-group v-if="!readonly" v-model="localFormData.chargeMode" :disabled="readonly">
<el-radio border :label="0">免费</el-radio>
<el-radio border :label="1">定价(赛事)</el-radio>
<el-radio border :label="2">单价(赛事)</el-radio>
<el-radio border :label="3">单价(项目)</el-radio>
</el-radio-group>
<span v-else>
{{ getChargeModeText(localFormData.chargeMode) }}
</span>
</el-form-item>
<el-form-item label="价格" prop="price" v-if="localFormData.chargeMode === 1 || localFormData.chargeMode === 2">
<el-input-number v-if="!readonly" v-model="localFormData.price" :min="0" :step="1" style="width: 100%" :disabled="readonly" />
<span v-else>{{ localFormData.price ?? '-' }}</span>
</el-form-item>
<!-- 单价(项目)模式:逐项目设置价格 -->
<el-form-item label="项目单价" v-if="localFormData.chargeMode === 3">
<div class="project-fee-list">
<div v-if="!localFormData.projects || !localFormData.projects.length" class="project-fee-empty">
暂无项目
</div>
<el-table v-else :data="localFormData.projects" border style="width: 100%">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column prop="name" label="项目名称" min-width="160" />
<el-table-column label="价格(元)" width="200" align="center">
<template #default="{ row }">
<el-input-number
v-if="!readonly"
v-model="row.fee"
:min="0"
:step="1"
:precision="2"
style="width: 160px"
placeholder="请输入价格"
/>
<span v-else>{{ row.fee !== undefined && row.fee !== null ? row.fee : '-' }}</span>
</template>
</el-table-column>
</el-table>
</div>
</el-form-item>
</el-form>
</template>
<script setup>
import { ref, watch } from 'vue';
const props = defineProps({
modelValue: { type: Object, required: true },
readonly: { type: Boolean, default: false }
});
const emit = defineEmits(['update:modelValue']);
const formRef = ref(null);
const localFormData = ref({
chargeMode: 0,
price: 0,
merchantId: undefined,
merchantName: '',
creditCode: '',
bankName: '',
accountNumber: '',
businessLicenseUrl: '',
projects: [],
});
const getChargeModeText = (mode) => {
if (mode === 0 || mode === '0' || mode === 'Free' || mode === 'free') return '免费';
if (mode === 1 || mode === '1' || mode === 'FixedPrice') return '定价(赛事)';
if (mode === 2 || mode === '2' || mode === 'UnitPriceEvent') return '单价(赛事)';
if (mode === 3 || mode === '3' || mode === 'UnitPriceProject') return '单价(项目)';
return '-';
};
// 标准化收费模式为数字
const normalizeChargeMode = (mode) => {
if (mode === 0 || mode === '0' || mode === 'Free' || mode === 'free') return 0;
if (mode === 1 || mode === '1' || mode === 'FixedPrice') return 1;
if (mode === 2 || mode === '2' || mode === 'UnitPriceEvent') return 2;
if (mode === 3 || mode === '3' || mode === 'UnitPriceProject') return 3;
return 0;
};
watch(
() => props.modelValue,
(v) => {
if (!v) return;
// 直接复制所有属性,强制覆盖
localFormData.value.chargeMode = normalizeChargeMode(v.chargeMode);
localFormData.value.price = v.price ?? 0;
localFormData.value.merchantId = v.merchantId;
localFormData.value.merchantName = v.merchantName ?? '';
localFormData.value.creditCode = v.creditCode ?? '';
localFormData.value.bankName = v.bankName ?? '';
localFormData.value.accountNumber = v.accountNumber ?? '';
localFormData.value.businessLicenseUrl = v.businessLicenseUrl ?? '';
localFormData.value.projects = v.projects ?? [];
},
{ deep: true, immediate: true }
);
watch(
localFormData,
(v) => emit('update:modelValue', { ...props.modelValue, ...v }),
{ deep: true }
);
const validatePrice = (rule, value, callback) => {
// 当收费模式为定价(赛事)或单价(赛事)时,价格必填
if (localFormData.value.chargeMode === 1 || localFormData.value.chargeMode === 2) {
if (value === '' || value === null || value === undefined || value <= 0) {
callback(new Error('请输入价格,且价格必须大于0'));
return;
}
}
// 单价(项目)模式:每个项目都必须有价格
if (localFormData.value.chargeMode === 3) {
const projects = localFormData.value.projects ?? [];
const invalid = projects.some((p) => p.fee === undefined || p.fee === null || p.fee === '' || p.fee < 0);
if (invalid) {
callback(new Error('请为所有项目设置价格(不能为空且不能小于0)'));
return;
}
}
callback();
};
const rules = {
chargeMode: [{ required: true, message: '请选择收费模式', trigger: 'change' }],
price: [
{ validator: validatePrice, trigger: ['change', 'blur'] }
],
};
const validate = async () => {
if (!formRef.value) return false;
// 单价(项目)模式:额外验证每个项目的 fee
if (localFormData.value.chargeMode === 3) {
const projects = localFormData.value.projects ?? [];
const invalid = projects.some((p) => p.fee === undefined || p.fee === null || p.fee === '' || p.fee < 0);
if (invalid) {
return Promise.reject('请为所有项目设置价格(不能为空且不能小于0)');
}
}
return await formRef.value.validate();
};
defineExpose({ validate });
</script>
<style scoped>
.step-fee {
padding: 0 20px;
}
.step-fee .el-form-item {
margin-bottom: 24px;
}
.fee-type-group {
display: flex;
width: 100%;
}
.fee-type-group .el-radio-button {
flex: 1;
}
.fee-type-group .el-radio-button__inner {
width: 100%;
}
.fee-description {
margin-bottom: 25px;
}
/* 项目单价列表 */
.project-fee-list {
width: 100%;
}
.project-fee-empty {
color: var(--el-text-color-secondary);
font-size: 14px;
padding: 12px 0;
}
</style>

View File

@ -65,7 +65,7 @@
<div style="height:50px;"></div>
<Step3ProjectConfig v-model:modelValue="formData.projectConfig" :readonly="true" />
<div style="height:50px;"></div>
<Step4FeeConfig v-model:modelValue="formData.feeConfig" :readonly="true" />
<UserStep4FeeConfig v-model:modelValue="formData.feeConfig" :readonly="true" />
<div style="height:100px;"></div>
</div>
<el-empty v-else description="暂无详情数据" />
@ -467,7 +467,7 @@ import QRCode from 'qrcodejs2-fixes';
import Step1BasicInfo from '/@/views/match/manage/Steps/Step1BasicInfo.vue';
import Step2Units from '/@/views/match/manage/Steps/Step2Units.vue';
import Step3ProjectConfig from '/@/views/match/manage/Steps/Step3ProjectConfig.vue';
import Step4FeeConfig from '/@/views/match/manage/Steps/Step4FeeConfig.vue';
import UserStep4FeeConfig from './UserStep4FeeConfig.vue';
import PreviewImage from '/@/components/PreviewImage/index.vue';
const tableRef = ref();
@ -638,9 +638,13 @@ const formData = ref({
eventName: '',
coverImage: '',
eventVideo: '',
eventType: '',
registrationType: '',
eventType: undefined,
registrationType: undefined,
pointsRuleId: undefined,
pointsRuleName: '',
provinceName: '',
cityName: '',
districtName: '',
registrationTime: [],
isNeedMaterial: false,
materialRemark: '',
@ -665,13 +669,14 @@ const formData = ref({
},
// 步骤4:费用与账户配置
feeConfig: {
chargeMode: 'Free',
chargeMode: 0,
price: 0,
merchantId: undefined,
merchantName: '',
creditCode: '',
bankName: '',
accountNumber: '',
projects: [],
},
});
@ -684,9 +689,13 @@ const clearDetailState = () => {
eventName: '',
coverImage: '',
eventVideo: '',
eventType: '',
registrationType: '',
eventType: undefined,
registrationType: undefined,
pointsRuleId: undefined,
pointsRuleName: '',
provinceName: '',
cityName: '',
districtName: '',
registrationTime: [],
isNeedMaterial: false,
materialRemark: '',
@ -703,13 +712,14 @@ const clearDetailState = () => {
registrationLimit: { individualProjectLimit: 1, teamProjectLimit: 1 },
},
feeConfig: {
chargeMode: 'Free',
chargeMode: 0,
price: 0,
merchantId: undefined,
merchantName: '',
creditCode: '',
bankName: '',
accountNumber: '',
projects: [],
},
};
};
@ -735,25 +745,35 @@ const openDetailDialog = async (row) => {
eventName: data.name || data.eventName || '',
coverImage: data.coverImage || '',
eventVideo: data.eventVideo || '',
eventType: data.type || data.eventType || '',
registrationType: data.registrationType || data.registrationMode || '',
eventType: data.type,
registrationType: data.registrationType,
pointsRuleId: data.pointsRuleId,
pointsRuleName: data.pointsRuleName || '',
provinceName: data.provinceName || '',
cityName: data.cityName || '',
districtName: data.districtName || '',
registrationTime: [data.registrationStartTime, data.registrationEndTime],
isNeedMaterial: !!data.isNeedMaterial,
materialRemark: data.materialRemark || '',
materialUploadTime: [data.materialUploadStartTime, data.materialUploadEndTime],
matchTime: [data.matchStartTime, data.matchEndTime],
regionIds: [data.provinceId, data.cityId, data.districtId].filter((x) => x !== undefined && x !== null),
detailAddress: data.detailAddress || '',
eventIntroduction: data.eventIntroduction || '',
detailAddress: data.address || data.detailAddress || '',
eventIntroduction: data.description || data.eventIntroduction || '',
customConfigJson: data.customConfigJson || '',
};
formData.value.units = { units: data.units || [] };
formData.value.projectConfig = {
projects: data.projects || [],
registrationLimit: data.registrationLimit || { individualProjectLimit: 1, teamProjectLimit: 1 },
registrationLimit: data.limitConfig || data.registrationLimit || { individualProjectLimit: 1, teamProjectLimit: 1 },
};
// 处理 feeConfig 数据,确保包含项目信息
formData.value.feeConfig = {
...formData.value.feeConfig,
...(data.feeConfig || {}),
chargeMode: data.feeConfig?.chargeMode,
projects: data.projects || []
};
formData.value.feeConfig = { ...formData.value.feeConfig, ...(data.feeConfig || {}) };
}
} catch {
ElMessage.error('赛事详情加载失败');