2026-02-12 21:38:29 +08:00
|
|
|
|
<template>
|
2026-03-18 18:03:12 +08:00
|
|
|
|
<div class="step-config">
|
|
|
|
|
|
<el-form ref="formRef" :model="localFormData" :rules="rules" label-position="top">
|
2026-03-23 06:14:31 +08:00
|
|
|
|
<el-card shadow="never" style="margin-bottom: 40px">
|
|
|
|
|
|
<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>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<el-collapse>
|
2026-03-25 18:25:26 +08:00
|
|
|
|
<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>
|
2026-03-23 06:14:31 +08:00
|
|
|
|
<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>
|
|
|
|
|
|
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
|
|
<el-card shadow="never">
|
2026-03-18 18:03:12 +08:00
|
|
|
|
<template #header>限报配置</template>
|
|
|
|
|
|
<el-row :gutter="12">
|
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
|
<el-form-item label="个人项目限报数" prop="registrationLimit.individualProjectLimit">
|
2026-03-23 06:14:31 +08:00
|
|
|
|
<el-input-number v-model="localFormData.registrationLimit.individualProjectLimit" :min="0"
|
|
|
|
|
|
:step="1" />
|
2026-03-18 18:03:12 +08:00
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
<el-col :span="12">
|
|
|
|
|
|
<el-form-item label="团队项目限报数" prop="registrationLimit.teamProjectLimit">
|
2026-03-23 06:14:31 +08:00
|
|
|
|
<el-input-number v-model="localFormData.registrationLimit.teamProjectLimit" :min="0"
|
|
|
|
|
|
:step="1" />
|
2026-03-18 18:03:12 +08:00
|
|
|
|
</el-form-item>
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
</el-row>
|
|
|
|
|
|
</el-card>
|
2026-03-23 06:14:31 +08:00
|
|
|
|
</el-form>
|
2026-03-18 18:03:12 +08:00
|
|
|
|
|
2026-03-23 06:14:31 +08:00
|
|
|
|
<el-dialog title="选择项目" v-model="isShowDialog">
|
|
|
|
|
|
<ProTable ref="tableRef" row-key="id" @selectionChange="selectionChange" :request-api="projectPageGet"
|
|
|
|
|
|
:columns="columns" :pagination="true" :tool-button="false">
|
|
|
|
|
|
<!-- <template #index="scope">
|
|
|
|
|
|
{{ scope.$index + 1 }}
|
|
|
|
|
|
</template> -->
|
|
|
|
|
|
<template #projectTypeId="scope">
|
|
|
|
|
|
{{ scope.row.projectTypeName }}
|
2026-03-18 18:03:12 +08:00
|
|
|
|
</template>
|
2026-03-23 06:14:31 +08:00
|
|
|
|
<template #completionCount="scope">
|
|
|
|
|
|
{{ scope.row.projectTypeId == '1003' || scope.row.projectTypeId == '1004' ? 1 :
|
|
|
|
|
|
scope.row.completionCount }}
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</ProTable>
|
|
|
|
|
|
<template #footer>
|
|
|
|
|
|
<span class="dialog-footer">
|
|
|
|
|
|
<el-button @click="isShowDialog = false">取消</el-button>
|
|
|
|
|
|
<el-button type="primary" @click="onSubmit">确定</el-button>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-dialog>
|
2026-03-18 18:03:12 +08:00
|
|
|
|
</div>
|
2026-02-12 21:38:29 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
2026-03-25 18:25:26 +08:00
|
|
|
|
import { ref, watch, nextTick } from 'vue';
|
2026-03-23 06:14:31 +08:00
|
|
|
|
import ProTable from '/@/components/ProTable/index.vue';
|
|
|
|
|
|
import { projectPageGet, projectDetailGet } from '/@/api/match/project';
|
|
|
|
|
|
import { ElMessage } from 'element-plus';
|
2026-03-18 18:03:12 +08:00
|
|
|
|
|
|
|
|
|
|
const props = defineProps({ modelValue: { type: Object, required: true } });
|
|
|
|
|
|
const emit = defineEmits(['update:modelValue']);
|
|
|
|
|
|
|
|
|
|
|
|
const formRef = ref(null);
|
|
|
|
|
|
const localFormData = ref({
|
|
|
|
|
|
projects: [],
|
|
|
|
|
|
registrationLimit: { individualProjectLimit: 1, teamProjectLimit: 1 },
|
|
|
|
|
|
});
|
2026-03-23 06:14:31 +08:00
|
|
|
|
const genderList = ref(['全部', '男', '女'])
|
2026-03-19 01:20:23 +08:00
|
|
|
|
const addProject = () => {
|
2026-03-23 06:14:31 +08:00
|
|
|
|
timeProjects.value = [];
|
|
|
|
|
|
isShowDialog.value = true;
|
2026-03-19 01:20:23 +08:00
|
|
|
|
};
|
2026-03-23 06:14:31 +08:00
|
|
|
|
const timeProjects = ref([])
|
|
|
|
|
|
const selectionChange = (list) => {
|
|
|
|
|
|
console.log(list)
|
|
|
|
|
|
timeProjects.value = list
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-25 18:25:26 +08:00
|
|
|
|
const onSubmit = async () => {
|
|
|
|
|
|
// 一次性拉取详情,避免 forEach(async) 导致多次触发更新/竞态
|
|
|
|
|
|
localFormData.value.projects = [];
|
|
|
|
|
|
const selected = timeProjects.value ?? [];
|
|
|
|
|
|
if (!selected.length) {
|
|
|
|
|
|
isShowDialog.value = false;
|
|
|
|
|
|
return;
|
2026-03-23 06:14:31 +08:00
|
|
|
|
}
|
2026-03-25 18:25:26 +08:00
|
|
|
|
|
|
|
|
|
|
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;
|
2026-03-23 06:14:31 +08:00
|
|
|
|
isShowDialog.value = false;
|
2026-03-25 18:25:26 +08:00
|
|
|
|
};
|
2026-03-23 06:14:31 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const isShowDialog = ref(false);
|
|
|
|
|
|
|
|
|
|
|
|
const columns = ref([
|
|
|
|
|
|
{
|
|
|
|
|
|
prop: 'index',
|
|
|
|
|
|
label: '序号',
|
|
|
|
|
|
align: 'center',
|
|
|
|
|
|
type: 'selection'
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
prop: 'name',
|
|
|
|
|
|
label: '项目名称',
|
|
|
|
|
|
align: 'center',
|
|
|
|
|
|
search: {
|
|
|
|
|
|
el: 'input',
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
prop: 'projectTypeId',
|
|
|
|
|
|
label: '项目类型',
|
|
|
|
|
|
search: {
|
|
|
|
|
|
el: 'select',
|
|
|
|
|
|
},
|
|
|
|
|
|
enum: [
|
|
|
|
|
|
{ label: '全部', value: 0 },
|
|
|
|
|
|
{ label: '团队速度', value: 1001 },
|
|
|
|
|
|
{ label: '团队花样', value: 1002 },
|
|
|
|
|
|
{ label: '个人速度', value: 1003 },
|
|
|
|
|
|
{ label: '个人花样', value: 1004 },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
prop: 'completionCount',
|
|
|
|
|
|
label: '完成人数',
|
|
|
|
|
|
},
|
|
|
|
|
|
{ prop: 'fee', label: '费用(元)' },
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-19 01:20:23 +08:00
|
|
|
|
const removeProject = (idx) => localFormData.value.projects.splice(idx, 1);
|
|
|
|
|
|
|
2026-03-25 18:25:26 +08:00
|
|
|
|
const isSyncingFromParent = ref(false);
|
|
|
|
|
|
|
|
|
|
|
|
const normalizeFromParent = async (v) => {
|
|
|
|
|
|
isSyncingFromParent.value = true;
|
2026-03-18 18:03:12 +08:00
|
|
|
|
localFormData.value.registrationLimit = v?.registrationLimit ?? { individualProjectLimit: 1, teamProjectLimit: 1 };
|
2026-03-25 18:25:26 +08:00
|
|
|
|
localFormData.value.projects = (v?.projects ?? []).map((p) => ({
|
|
|
|
|
|
...p,
|
|
|
|
|
|
limitCount: p?.limitCount ?? 0,
|
|
|
|
|
|
}));
|
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
isSyncingFromParent.value = false;
|
2026-03-23 06:14:31 +08:00
|
|
|
|
// localFormData.value.projects = (v?.projects ?? []).map((p) => ({
|
|
|
|
|
|
// ...p,
|
|
|
|
|
|
// // 用文本编辑组别,提交时再解析成 groups[]
|
|
|
|
|
|
// groupsText: Array.isArray(p.groups)
|
|
|
|
|
|
// ? p.groups.map((g) => `${g.groupName}|${g.birthDateStart ?? ''}|${g.birthDateEnd ?? ''}|${g.gender ?? ''}`).join('\n')
|
|
|
|
|
|
// : '',
|
|
|
|
|
|
// }));
|
|
|
|
|
|
// if (!localFormData.value.projects.length) ElMessage.error('请选择项目!');
|
2026-03-18 18:03:12 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
watch(
|
|
|
|
|
|
() => props.modelValue,
|
2026-03-25 18:25:26 +08:00
|
|
|
|
async (v) => await normalizeFromParent(v),
|
2026-03-18 18:03:12 +08:00
|
|
|
|
{ deep: true, immediate: true }
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
watch(
|
|
|
|
|
|
localFormData,
|
|
|
|
|
|
(v) => {
|
2026-03-25 18:25:26 +08:00
|
|
|
|
// 防止父子双向同步时形成递归更新
|
|
|
|
|
|
if (isSyncingFromParent.value) return;
|
2026-03-23 06:14:31 +08:00
|
|
|
|
// const projects = v.projects.map((p) => ({
|
|
|
|
|
|
// id: p.id,
|
|
|
|
|
|
// name: p.name,
|
|
|
|
|
|
// projectTypeId: p.projectTypeId,
|
|
|
|
|
|
// projectTypeName: p.projectTypeName,
|
|
|
|
|
|
// completionCount: p.completionCount,
|
|
|
|
|
|
// durationMinutes: p.durationMinutes,
|
|
|
|
|
|
// durationSeconds: p.durationSeconds,
|
|
|
|
|
|
// fee: p.fee,
|
|
|
|
|
|
// limitCount: p.limitCount,
|
|
|
|
|
|
// groups: p.groups,
|
|
|
|
|
|
// }));
|
|
|
|
|
|
emit('update:modelValue', { ...props.modelValue, registrationLimit: v.registrationLimit, projects: v.projects });
|
2026-03-18 18:03:12 +08:00
|
|
|
|
},
|
|
|
|
|
|
{ deep: true }
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2026-03-19 01:20:23 +08:00
|
|
|
|
/* addProject and removeProject are declared above to avoid initialization order issues */
|
2026-03-18 18:03:12 +08:00
|
|
|
|
|
|
|
|
|
|
const rules = {
|
|
|
|
|
|
'registrationLimit.individualProjectLimit': [{ required: true, message: '请填写个人项目限报数', trigger: 'change' }],
|
|
|
|
|
|
'registrationLimit.teamProjectLimit': [{ required: true, message: '请填写团队项目限报数', trigger: 'change' }],
|
|
|
|
|
|
};
|
2026-02-12 21:38:29 +08:00
|
|
|
|
|
|
|
|
|
|
const validate = async () => {
|
2026-03-18 18:03:12 +08:00
|
|
|
|
if (!formRef.value) return false;
|
|
|
|
|
|
return await formRef.value.validate();
|
|
|
|
|
|
};
|
2026-02-12 21:38:29 +08:00
|
|
|
|
|
2026-03-18 18:03:12 +08:00
|
|
|
|
defineExpose({ validate });
|
2026-02-12 21:38:29 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.step-config {
|
2026-03-23 06:14:31 +08:00
|
|
|
|
padding: 0 20px;
|
2026-02-12 21:38:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.step-config .el-form-item {
|
2026-03-23 06:14:31 +08:00
|
|
|
|
margin-bottom: 24px;
|
2026-02-12 21:38:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-25 18:25:26 +08:00
|
|
|
|
.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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-12 21:38:29 +08:00
|
|
|
|
.input-hint {
|
2026-03-23 06:14:31 +08:00
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #909399;
|
|
|
|
|
|
margin-top: 5px;
|
2026-02-12 21:38:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.switch-hint {
|
2026-03-23 06:14:31 +08:00
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
color: #909399;
|
|
|
|
|
|
margin-top: 5px;
|
2026-02-12 21:38:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.type-info-card {
|
2026-03-23 06:14:31 +08:00
|
|
|
|
margin-top: 20px;
|
|
|
|
|
|
background: #f5f7fa;
|
2026-02-12 21:38:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.card-header {
|
2026-03-23 06:14:31 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
font-weight: 600;
|
2026-02-12 21:38:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.type-list {
|
2026-03-23 06:14:31 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 12px;
|
2026-02-12 21:38:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.type-item {
|
2026-03-23 06:14:31 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 15px;
|
|
|
|
|
|
padding: 10px 15px;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
border: 1px solid #e4e7ed;
|
2026-02-12 21:38:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.type-name {
|
2026-03-23 06:14:31 +08:00
|
|
|
|
font-weight: 600;
|
|
|
|
|
|
color: #409eff;
|
|
|
|
|
|
min-width: 120px;
|
2026-02-12 21:38:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.type-desc {
|
2026-03-23 06:14:31 +08:00
|
|
|
|
color: #606266;
|
|
|
|
|
|
font-size: 14px;
|
2026-02-12 21:38:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
</style>
|