YD_Event.Web/src/views/match/manage/Steps/Step3ProjectConfig.vue

312 lines
8.2 KiB
Vue
Raw Normal View History

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>