Merge branch 'main' of http://8.153.108.90:3000/YD/YD_Event.Web
This commit is contained in:
commit
03e5670af4
@ -9,7 +9,7 @@ import { http } from '../../utils/request';
|
||||
* @param {number} [data.pageNo] - 当前页码
|
||||
* @param {number} [data.pageSize] - 每页条数
|
||||
* @param {string} [data.name] - 优惠券名称 (模糊查询)
|
||||
* @param {number} [data.type] - 优惠券类型 (1:满减券, 2:折扣券, 3:代金券)
|
||||
* @param {number} [data.type] - 优惠券类型 (0:折扣券, 1:定额券)
|
||||
* @returns {Promise<{
|
||||
* pageNo: number,
|
||||
* pageSize: number,
|
||||
@ -41,7 +41,7 @@ export const couponPage = (data) => {
|
||||
* 添加优惠券
|
||||
* @param {Object} data - 优惠券信息
|
||||
* @param {string} data.name - 优惠券名称
|
||||
* @param {number} data.type - 优惠券类型 (1:满减券, 2:折扣券, 3:代金券)
|
||||
* @param {number} data.type - 优惠券类型 (0:折扣券, 1:定额券)
|
||||
* @param {number} data.value - 优惠券面值
|
||||
* @param {string} data.startTime - 开始时间 (格式:2026-03-08T18:23:19.664Z)
|
||||
* @param {string} data.endTime - 结束时间 (格式:2026-03-08T18:23:19.664Z)
|
||||
@ -57,7 +57,7 @@ export const couponAdd = (data) => {
|
||||
* @param {Object} data - 优惠券信息
|
||||
* @param {number} data.id - 优惠券ID
|
||||
* @param {string} data.name - 优惠券名称
|
||||
* @param {number} data.type - 优惠券类型 (1:满减券, 2:折扣券, 3:代金券)
|
||||
* @param {number} data.type - 优惠券类型 (0:折扣券, 1:定额券)
|
||||
* @param {number} data.value - 优惠券面值
|
||||
* @param {string} data.startTime - 开始时间 (格式:2026-03-08T18:23:19.665Z)
|
||||
* @param {string} data.endTime - 结束时间 (格式:2026-03-08T18:23:19.665Z)
|
||||
|
||||
@ -6,14 +6,16 @@ import { http } from '../../utils/request';
|
||||
/**
|
||||
* 分页查询参赛记录
|
||||
* @param {Object} params - 查询参数
|
||||
* @param {number} [params.AppUserId] - 普通用户ID
|
||||
* @param {number} [params.MerchantId] - 商户ID
|
||||
* @param {number} [params.AppUserId] - 普通用户ID(AppUser表的Id)
|
||||
* @param {number} [params.SysUserId] - 系统用户ID(SysUser表的Id,用于单位用户)
|
||||
* @param {number} [params.MerchantId] - 商户ID(已弃用,使用SysUserId)
|
||||
* @param {string} [params.EventName] - 赛事名称 (模糊查询)
|
||||
* @param {number} [params.RegistrationStatus] - 报名状态 (0:待支付, 1:已支付, 2:已取消, 3:已退款, 4:已完成)
|
||||
* @param {number} [params.PaymentStatus] - 支付状态 (0:未支付/待审核, 1:已支付/审核通过, 2:支付失败/审核拒绝, 3:已退款, 4:已取消)
|
||||
* @param {string} [params.StartTime] - 开始时间
|
||||
* @param {string} [params.EndTime] - 结束时间
|
||||
* @param {number} [params.PageNo] - 当前页码
|
||||
* @param {number} [params.PageSize] - 每页条数
|
||||
* @param {number} [params.refresh] - 刷新标记(用于强制刷新数据)
|
||||
* @returns {Promise<{
|
||||
* pageNo: number,
|
||||
* pageSize: number,
|
||||
|
||||
@ -8,7 +8,7 @@ import { http } from '../../utils/request';
|
||||
* @param {Object} params - 查询参数
|
||||
* @param {number} [params.EventId] - 赛事ID
|
||||
* @param {string} [params.ProjectName] - 项目名称 (模糊查询)
|
||||
* @param {number} [params.ProjectTypeId] - 项目类型ID
|
||||
* @param {number} [params.ProjectType] - 项目类型
|
||||
* @param {number} [params.PageNo] - 当前页码
|
||||
* @param {number} [params.PageSize] - 页码容量
|
||||
*/
|
||||
@ -21,7 +21,7 @@ export const projectPageGet = (params) => {
|
||||
* @param {Object} data - 项目信息
|
||||
* @param {number} data.eventId - 赛事ID
|
||||
* @param {string} data.name - 项目名称
|
||||
* @param {number} data.projectTypeId - 项目类型ID
|
||||
* @param {number} data.projectType - 项目类型
|
||||
* @param {number} data.completionCount - 完成人数
|
||||
* @param {number} data.fee - 费用
|
||||
* @param {number} data.durationMinutes - 时长(分钟)
|
||||
@ -44,7 +44,7 @@ export const projectAddPost = (data) => {
|
||||
* @param {number} data.id - 项目ID
|
||||
* @param {number} data.eventId - 赛事ID
|
||||
* @param {string} data.name - 项目名称
|
||||
* @param {number} data.projectTypeId - 项目类型ID
|
||||
* @param {number} data.projectType - 项目类型
|
||||
* @param {number} data.completionCount - 完成次数/达标次数
|
||||
* @param {number} data.fee - 费用
|
||||
* @param {number} data.durationMinutes - 时长(分钟)
|
||||
|
||||
@ -93,11 +93,40 @@ export const useTable = (
|
||||
for (let key in state.searchParam) {
|
||||
// * 某些情况下参数为 false/0 也应该携带参数
|
||||
if (state.searchParam[key] || state.searchParam[key] === false || state.searchParam[key] === 0) {
|
||||
nowSearchParam[key] = state.searchParam[key];
|
||||
// 处理日期范围参数:如果是数组,拆分为 StartTime 和 EndTime
|
||||
if (Array.isArray(state.searchParam[key]) && state.searchParam[key].length === 2) {
|
||||
let startTime = state.searchParam[key][0];
|
||||
let endTime = state.searchParam[key][1];
|
||||
|
||||
// 如果是 Date 对象,格式化为本地日期字符串,避免时区转换问题
|
||||
if (startTime instanceof Date) {
|
||||
startTime = formatLocalDate(startTime);
|
||||
}
|
||||
if (endTime instanceof Date) {
|
||||
endTime = formatLocalDate(endTime);
|
||||
}
|
||||
|
||||
nowSearchParam['StartTime'] = startTime;
|
||||
nowSearchParam['EndTime'] = endTime;
|
||||
} else {
|
||||
nowSearchParam[key] = state.searchParam[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
Object.assign(state.totalParam, nowSearchParam, isPageable ? pageParam.value : {});
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 格式化日期为本地日期字符串,避免时区转换
|
||||
* @param date Date 对象
|
||||
* @returns YYYY-MM-DD 格式的字符串
|
||||
* */
|
||||
const formatLocalDate = (date: Date): string => {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${year}-${month}-${day}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 更新分页信息
|
||||
|
||||
@ -79,10 +79,11 @@ service.interceptors.response.use(
|
||||
ElMessage.error('无权访问');
|
||||
return response;
|
||||
}
|
||||
// 处理规范化结果错误
|
||||
// 处理规范化结果错误
|
||||
if (res.statusCode !== 200) {
|
||||
var message = JSON.stringify(res.errors);
|
||||
ElMessage.error(message);
|
||||
return Promise.reject(new Error(res.errors || 'Request failed'));
|
||||
}
|
||||
return response;
|
||||
},
|
||||
|
||||
@ -13,10 +13,10 @@
|
||||
<span class="ml10">{{ $t(scope.row.name) }}</span>
|
||||
</template>
|
||||
<template #type="scope">
|
||||
{{ scope.row.type == 1 ? '折扣券' : '代金券' }}
|
||||
{{ scope.row.type == 0 ? '折扣券' : '定额券' }}
|
||||
</template>
|
||||
<template #value="scope">
|
||||
{{ scope.row.type == 1 ? scope.row.value + '%' : scope.row.value + '元' }}
|
||||
{{ scope.row.type == 0 ? scope.row.value + '%' : scope.row.value + '元' }}
|
||||
</template>
|
||||
<template #startTime="scope">
|
||||
{{ formatDate(scope.row.startTime) }} - {{ formatDate(scope.row.endTime) }}
|
||||
@ -49,20 +49,20 @@
|
||||
<!-- 优惠券类型 -->
|
||||
<el-form-item label="优惠券类型" prop="type">
|
||||
<el-select v-model="formData.type" placeholder="请选择优惠券类型">
|
||||
<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="1"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 填写折扣 -->
|
||||
<el-form-item label="填写折扣" prop="value" v-if="formData.type == '1'">
|
||||
<el-form-item label="填写折扣" prop="value" v-if="formData.type === 0">
|
||||
<el-input v-model="formData.value" placeholder="请输入折扣百分比">
|
||||
<template #append>%</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 填写优惠金额 -->
|
||||
<el-form-item label="填写优惠金额" prop="value" v-if="formData.type == '2'">
|
||||
<el-form-item label="填写优惠金额" prop="value" v-if="formData.type === 1">
|
||||
<el-input v-model="formData.value" placeholder="请输入优惠金额" class="fee-input">
|
||||
<template #append>元</template>
|
||||
</el-input>
|
||||
@ -70,9 +70,9 @@
|
||||
|
||||
<!-- 选择有效期 -->
|
||||
<el-form-item label="选择有效期" prop="startTime">
|
||||
<el-date-picker type="daterange" v-model="formData.time" start-placeholder="开始日期"
|
||||
end-placeholder="结束日期" style="width: 100%;" value-format="YYYY-MM-DD" @change="(val) => {
|
||||
console.log('选择的日期范围:', val);
|
||||
<el-date-picker type="datetimerange" v-model="formData.time" start-placeholder="开始日期时间"
|
||||
end-placeholder="结束日期时间" style="width: 100%;" value-format="YYYY-MM-DD HH:mm" @change="(val) => {
|
||||
console.log('选择的日期时间范围:', val);
|
||||
formData.startTime = val[0];
|
||||
formData.endTime = val[1];
|
||||
}"></el-date-picker>
|
||||
@ -120,14 +120,39 @@ import { auths } from '/@/utils/authFunction';
|
||||
import ProTable from '/@/components/ProTable/index.vue';
|
||||
import { couponPage, couponAdd, couponEdit, couponDelete, couponEventlistGet, couponGeneratecodePost } from '/@/api/match/coupon';
|
||||
|
||||
// 格式化日期,只显示年月日
|
||||
// 格式化日期,显示完整时间(YYYY-MM-DD HH:MM)
|
||||
const formatDate = (dateStr) => {
|
||||
if (!dateStr) return '';
|
||||
if (/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
|
||||
// 如果已经是日期时间格式,直接返回(或调整格式)
|
||||
try {
|
||||
const date = new Date(dateStr);
|
||||
if (isNaN(date.getTime())) return dateStr;
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
||||
} catch (e) {
|
||||
return dateStr;
|
||||
}
|
||||
};
|
||||
|
||||
// 格式化日期为选择器用的格式
|
||||
const formatDateTimePicker = (dateStr) => {
|
||||
if (!dateStr) return '';
|
||||
try {
|
||||
const date = new Date(dateStr);
|
||||
if (isNaN(date.getTime())) return dateStr;
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
||||
} catch (e) {
|
||||
return dateStr;
|
||||
}
|
||||
// 截取前10个字符(YYYY-MM-DD)
|
||||
return dateStr.slice(0, 10);
|
||||
};
|
||||
|
||||
// 定义表单引用类型
|
||||
@ -188,7 +213,7 @@ const columns = ref([
|
||||
el: 'input',
|
||||
},
|
||||
},
|
||||
{ prop: 'type', label: '优惠券类型', search: { el: 'select', }, enum: [{ label: '全部', value: '' }, { label: '折扣', value: 1 }, { label: '定价', value: 2 },] },
|
||||
{ prop: 'type', label: '优惠券类型', search: { el: 'select', }, enum: [{ label: '全部', value: '' }, { label: '折扣', value: 0 }, { label: '定额', value: 1 },] },
|
||||
{ prop: 'value', label: '详情', },
|
||||
{ prop: 'startTime', label: '有效期' },
|
||||
{ prop: 'eventNames', label: '可用赛事' },
|
||||
@ -209,9 +234,9 @@ const onOpenMenu = async (row) => {
|
||||
if (row) {
|
||||
formData.value = {
|
||||
...row,
|
||||
startTime: formatDate(row.startTime),
|
||||
endTime: formatDate(row.endTime),
|
||||
time: [formatDate(row.startTime), formatDate(row.endTime)],
|
||||
startTime: formatDateTimePicker(row.startTime),
|
||||
endTime: formatDateTimePicker(row.endTime),
|
||||
time: [formatDateTimePicker(row.startTime), formatDateTimePicker(row.endTime)],
|
||||
};
|
||||
} else {
|
||||
formData.value = {
|
||||
|
||||
@ -28,7 +28,7 @@
|
||||
start-placeholder="开始日期" end-placeholder="结束日期"
|
||||
:model-value="[group.birthDateStart, group.birthDateEnd]"
|
||||
@update:modelValue="(v) => { group.birthDateStart = v?.[0]; group.birthDateEnd = v?.[1]; }" :disabled="readonly" />
|
||||
<span v-else class="group-row__range">{{ group.birthDateStart }} ~ {{ group.birthDateEnd }}</span>
|
||||
<span v-else class="group-row__range">{{ formatGroupDate(group) }}</span>
|
||||
|
||||
<el-select v-if="!readonly" v-model="group.gender" class="group-row__gender" placeholder="性别" :disabled="readonly">
|
||||
<el-option label="全部" :value="3" />
|
||||
@ -128,7 +128,7 @@
|
||||
<!-- <template #index="scope">
|
||||
{{ scope.$index + 1 }}
|
||||
</template> -->
|
||||
<template #projectTypeId="scope">
|
||||
<template #projectType="scope">
|
||||
{{ scope.row.projectTypeName }}
|
||||
</template>
|
||||
<template #completionCount="scope">
|
||||
@ -150,6 +150,7 @@ import { ref, watch, nextTick, computed } from 'vue';
|
||||
import ProTable from '/@/components/ProTable/index.vue';
|
||||
import { projectPageGet, projectDetailGet } from '/@/api/match/project';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { formatDate } from '/@/utils/formatTime';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: Object, required: true },
|
||||
@ -168,6 +169,20 @@ const localFormData = ref({
|
||||
registrationLimit: { individualProjectLimit: 1, teamProjectLimit: 1 },
|
||||
});
|
||||
const genderList = ref(['全部', '男', '女'])
|
||||
|
||||
// 格式化组别的年龄段(出生日期范围)
|
||||
const formatGroupDate = (item) => {
|
||||
const start = item?.birthDateStart;
|
||||
const end = item?.birthDateEnd;
|
||||
if (!start && !end) return '';
|
||||
const fmt = (v) => {
|
||||
if (!v) return '';
|
||||
try { return formatDate(new Date(v), 'YYYY-mm-dd'); } catch { return String(v); }
|
||||
};
|
||||
if (start && end) return `${fmt(start)} ~ ${fmt(end)}`;
|
||||
return fmt(start || end);
|
||||
};
|
||||
|
||||
const addProject = () => {
|
||||
isShowDialog.value = true;
|
||||
// 打开弹窗时,用当前已选项目初始化 timeProjects,以便确定后保留勾选
|
||||
@ -248,17 +263,17 @@ const columns = ref([
|
||||
},
|
||||
},
|
||||
{
|
||||
prop: 'projectTypeId',
|
||||
prop: 'projectType',
|
||||
label: '项目类型',
|
||||
search: {
|
||||
el: 'select',
|
||||
},
|
||||
enum: [
|
||||
{ label: '全部', value: 0 },
|
||||
{ label: '团队速度', value: 1001 },
|
||||
{ label: '团队花样', value: 1002 },
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '个人速度', value: 1003 },
|
||||
{ label: '个人花样', value: 1004 },
|
||||
{ label: '团队速度', value: 1005 },
|
||||
{ label: '团队花样', value: 1006 },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@ -201,9 +201,22 @@ const columns = reactive([
|
||||
{ prop: 'action', label: '操作', align: 'center', width: 200, fixed: 'right', isShow: auths(['event:edit', 'event:delete']) },
|
||||
]);
|
||||
|
||||
const exportableColumns = computed(() =>
|
||||
columns.filter((item) => item?.prop && item?.label && !['action', 'index'].includes(item.prop))
|
||||
);
|
||||
const exportableColumns = computed(() => [
|
||||
{ prop: 'eventName', label: '赛事名称' },
|
||||
{ prop: 'eventType', label: '赛事类型' },
|
||||
{ prop: 'chargeMode', label: '收费模式' },
|
||||
{ prop: 'merchantName', label: '收款商户' },
|
||||
{ prop: 'registrationType', label: '报名方式' },
|
||||
{ prop: 'eventStatus', label: '赛事状态' },
|
||||
{ prop: 'registrationTime', label: '报名起止时间' },
|
||||
{ prop: 'materialUploadEndTime', label: '上传素材截至时间' },
|
||||
{ prop: 'matchTime', label: '比赛起止时间' },
|
||||
{ prop: 'detailAddress', label: '赛事地址' },
|
||||
{ prop: 'hostUnit', label: '主办单位' },
|
||||
{ prop: 'organizerUnit', label: '承办单位' },
|
||||
{ prop: 'coOrganizerUnit', label: '协办单位' },
|
||||
{ prop: 'sponsorUnit', label: '赞助单位' }
|
||||
]);
|
||||
|
||||
// 定义变量内容
|
||||
const menuDialogRef = ref();
|
||||
|
||||
@ -69,24 +69,44 @@
|
||||
</ProTable>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="赛事编排" name="third">
|
||||
<el-descriptions :title="item.projectName" v-for="(item, index) in eventlist" :key="index">
|
||||
<el-descriptions-item label="排序:">
|
||||
<el-input-number v-model="item.sortIndex" :min="1" :step="1" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="设置场地数量:">
|
||||
<el-input-number v-model="item.venueCount" :min="0" :step="1" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="类型:">{{ item.projectType }}</el-descriptions-item>
|
||||
<el-descriptions-item label="时长:">{{ item.totalDurationMinutes }}:{{ item.durationSeconds
|
||||
}}</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 70px;">参与人数: {{ group.participantCount }}</span>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div style="text-align: right; margin-top: 20px;">
|
||||
<el-button type="primary" @click="onSave"> 保存修改 </el-button>
|
||||
<el-button type="primary" @click="getArr"> 导出编排 </el-button>
|
||||
<div class="arrangement-container">
|
||||
<div class="sort-hint-top">
|
||||
<el-icon><Rank /></el-icon>
|
||||
<span>拖动项目卡片可调整顺序,按最终顺序导出</span>
|
||||
</div>
|
||||
<div ref="arrangementContainerRef" class="arrangement-cards-wrapper">
|
||||
<div class="arrangement-card" v-for="(item, index) in eventlist" :key="index">
|
||||
<div class="card-left">
|
||||
<div class="project-name">{{ item.projectName }}</div>
|
||||
<div class="project-type">类型: {{ item.projectType }}</div>
|
||||
<div class="project-duration">时长: {{ item.totalDurationMinutes }}分{{ item.durationSeconds }}秒</div>
|
||||
</div>
|
||||
|
||||
<div class="card-middle">
|
||||
<div class="group-tag" v-for="(group, idx) in item.groups" :key="idx">
|
||||
<span class="group-name">{{ group.groupName }}</span>
|
||||
<span class="group-count">{{ group.participantCount }}{{ getParticipantUnit(item.projectType) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-right">
|
||||
<div class="venue-box">
|
||||
<div class="box-label">设置场地数量</div>
|
||||
<el-input-number v-model="item.venueCount" :min="0" :step="1" size="large" />
|
||||
</div>
|
||||
<div class="duration-box">
|
||||
<div class="box-label">项目所需时长</div>
|
||||
<div class="duration-value">{{ item.totalDurationMinutes }}'</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="action-bar">
|
||||
<el-button type="default" @click="previewArrangement">预览</el-button>
|
||||
<el-button type="primary" @click="onSave">保存修改</el-button>
|
||||
<el-button type="primary" @click="getArr">导出</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="成绩与证书" name="fourth">
|
||||
@ -153,15 +173,66 @@
|
||||
@change="onScoreFileChange"
|
||||
/>
|
||||
|
||||
<!-- 赛事编排预览对话框 -->
|
||||
<el-dialog
|
||||
v-model="previewDialogVisible"
|
||||
title="赛事编排预览"
|
||||
width="90%"
|
||||
style="min-height: 70vh;"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div class="preview-container">
|
||||
<div class="preview-title">{{ previewEventName }}</div>
|
||||
<div class="preview-table-wrapper">
|
||||
<table class="preview-table" border="1" cellpadding="6" cellspacing="0">
|
||||
<thead>
|
||||
<tr class="header-row">
|
||||
<th class="time-col">时间</th>
|
||||
<th class="project-col">参赛项目</th>
|
||||
<th class="group-col">参赛组别</th>
|
||||
<th class="team-col">参赛队伍</th>
|
||||
<th class="athlete-col">运动员姓名</th>
|
||||
<th class="session-col">场次</th>
|
||||
<th class="venue-col">场地</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="(item, index) in previewData" :key="index">
|
||||
<tr :class="index % 2 === 0 ? 'data-row-even' : 'data-row-odd'">
|
||||
<td
|
||||
v-if="!isTimeMerged(index)"
|
||||
:rowspan="getTimeRowspan(index)"
|
||||
class="time-cell"
|
||||
>
|
||||
{{ formatTime(item.timeRange) }}
|
||||
</td>
|
||||
<td class="project-cell">{{ item.projectName }}</td>
|
||||
<td class="group-cell">{{ item.groupName }}</td>
|
||||
<td class="team-cell">{{ item.teamName || '-' }}</td>
|
||||
<td class="athlete-cell">{{ item.athleteName }}</td>
|
||||
<td class="session-cell">{{ item.sessionIndex }}</td>
|
||||
<td class="venue-cell">{{ item.venueIndex }}</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="previewDialogVisible = false">关闭</el-button>
|
||||
<el-button type="primary" @click="getArr">导出Excel</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, markRaw } from 'vue';
|
||||
import { ref, reactive, computed, onMounted, watch, nextTick } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { ArrowLeft, ArrowRight, Check, CircleCheckFilled, Refresh } from '@element-plus/icons-vue';
|
||||
import { eventPost, eventPut, eventDetailGet } from '/@/api/match/event';
|
||||
import { eventarrangementListGet, eventarrangementPut, eventarrangementExportPost } from '/@/api/match/eventarrangement';
|
||||
import { Check, View, Download, Rank } from '@element-plus/icons-vue';
|
||||
import { eventDetailGet } from '/@/api/match/event';
|
||||
import { eventarrangementListGet, eventarrangementPut, eventarrangementExportPost, eventarrangementPreviewPost } from '/@/api/match/eventarrangement';
|
||||
import { eventregistrationPagePost, eventregistrationBatchauditPost, eventregistrationMaterialurlsPost } from '/@/api/match/eventregistration';
|
||||
import { eventscorePageathletePost, eventscorePagecoachleaderPost,eventscoreImportscorePost, eventscoreDownloadtemplateGet, eventscoreExportcertificatePost } from '/@/api/match/eventscore';
|
||||
import Step1BasicInfo from './Steps/Step1BasicInfo.vue';
|
||||
@ -170,12 +241,20 @@ import Step3ProjectConfig from './Steps/Step3ProjectConfig.vue';
|
||||
import Step4FeeConfig from './Steps/Step4FeeConfig.vue';
|
||||
import ProTable from '/@/components/ProTable/index.vue';
|
||||
import PreviewImage from '/@/components/PreviewImage/index.vue';
|
||||
import Sortable from 'sortablejs';
|
||||
|
||||
|
||||
const isShowDialog = ref(false);
|
||||
const activeName = ref('first')
|
||||
const eventlist = ref([]);
|
||||
const importingScore = ref(false);
|
||||
const arrangementContainerRef = ref(null);
|
||||
let sortableInstance = null;
|
||||
|
||||
// 预览相关
|
||||
const previewDialogVisible = ref(false);
|
||||
const previewData = ref([]);
|
||||
const previewEventName = ref('赛事编排预览');
|
||||
const scoreFileInputRef = ref(null);
|
||||
const tableuu = ref();
|
||||
const tableCoach = ref();
|
||||
@ -230,9 +309,11 @@ const formData = ref({
|
||||
const openDialog = async (id) => {
|
||||
isShowDialog.value = true;
|
||||
const res = await eventDetailGet(id);
|
||||
initParam.value.eventId = id;
|
||||
initParam.value.id = id;
|
||||
const d = res?.data;
|
||||
if (d) {
|
||||
previewEventName.value = d.eventName || '赛事编排预览';
|
||||
formData.value.basicInfo = {
|
||||
...formData.value.basicInfo,
|
||||
id: d.id,
|
||||
@ -263,22 +344,48 @@ const openDialog = async (id) => {
|
||||
|
||||
const res2 = await eventarrangementListGet(id);
|
||||
eventlist.value = res2.data
|
||||
|
||||
await nextTick();
|
||||
initSortable();
|
||||
};
|
||||
|
||||
const initSortable = () => {
|
||||
if (!arrangementContainerRef.value) return;
|
||||
|
||||
if (sortableInstance) {
|
||||
sortableInstance.destroy();
|
||||
}
|
||||
|
||||
sortableInstance = new Sortable(arrangementContainerRef.value, {
|
||||
animation: 150,
|
||||
ghostClass: 'sort-ghost',
|
||||
dragClass: 'sort-drag',
|
||||
handle: '.arrangement-card',
|
||||
onEnd: (evt) => {
|
||||
const { oldIndex, newIndex } = evt;
|
||||
if (oldIndex === undefined || newIndex === undefined || oldIndex === newIndex) return;
|
||||
|
||||
const item = eventlist.value.splice(oldIndex, 1)[0];
|
||||
eventlist.value.splice(newIndex, 0, item);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const initParam = ref({
|
||||
refresh: false,
|
||||
id: '',
|
||||
eventId: 0,
|
||||
id: 0,
|
||||
})
|
||||
|
||||
const initParamCoach = computed(() => ({
|
||||
refresh: initParam.value.refresh,
|
||||
id: initParam.value.id,
|
||||
eventId: initParam.value.eventId,
|
||||
personType: 'Coach'
|
||||
}))
|
||||
|
||||
const initParamLeader = computed(() => ({
|
||||
refresh: initParam.value.refresh,
|
||||
id: initParam.value.id,
|
||||
eventId: initParam.value.eventId,
|
||||
personType: 'Leader'
|
||||
}))
|
||||
|
||||
@ -298,9 +405,16 @@ const columns = reactive([
|
||||
el: 'select',
|
||||
},
|
||||
enum: [
|
||||
{ label: '个人报名', value: 0 },
|
||||
{ label: '团队报名', value: 1 },
|
||||
{ label: '个人报名', value: 1 },
|
||||
{ label: '团队报名', value: 2 },
|
||||
],
|
||||
customRender: ({ text }) => {
|
||||
const modeMap = {
|
||||
1: '个人报名',
|
||||
2: '团队报名'
|
||||
};
|
||||
return modeMap[text] || text;
|
||||
}
|
||||
},
|
||||
{
|
||||
width: '120px',
|
||||
@ -319,6 +433,9 @@ const columns = reactive([
|
||||
{ label: '是', value: true },
|
||||
{ label: '否', value: false },
|
||||
],
|
||||
customRender: ({ text }) => {
|
||||
return text ? '已上传' : '未上传';
|
||||
}
|
||||
},
|
||||
{
|
||||
width: '120px',
|
||||
@ -328,10 +445,18 @@ const columns = reactive([
|
||||
el: 'select',
|
||||
},
|
||||
enum: [
|
||||
{ label: '线上', value: 0 },
|
||||
{ label: '线下', value: 1 },
|
||||
{ label: '线上', value: 2 },
|
||||
{ label: '全部', value: '' },
|
||||
],
|
||||
customRender: ({ record }) => {
|
||||
const methodMap = {
|
||||
0: '未选择',
|
||||
1: '线下',
|
||||
2: '线上'
|
||||
};
|
||||
return methodMap[record.paymentMethod] || '未知';
|
||||
}
|
||||
},
|
||||
{
|
||||
width: '120px', prop: 'paymentStatus', label: '支付状态',
|
||||
@ -344,10 +469,32 @@ const columns = reactive([
|
||||
{ label: '待审核', value: 2 },
|
||||
{ label: '全部', value: '' },
|
||||
],
|
||||
customRender: ({ text }) => {
|
||||
const statusMap = {
|
||||
0: '未支付',
|
||||
1: '已支付',
|
||||
2: '待审核',
|
||||
3: '支付失败',
|
||||
4: '已退款'
|
||||
};
|
||||
return statusMap[text] || '未知';
|
||||
}
|
||||
},
|
||||
{ width: '150px', prop: 'teamLeaderInfo', label: '领队信息',
|
||||
customRender: ({ text }) => {
|
||||
return text || '-';
|
||||
}
|
||||
},
|
||||
{ width: '150px', prop: 'coachInfo', label: '教练信息',
|
||||
customRender: ({ text }) => {
|
||||
return text || '-';
|
||||
}
|
||||
},
|
||||
{ width: '120px', prop: 'orderNo', label: '订单信息',
|
||||
customRender: ({ text }) => {
|
||||
return text || '-';
|
||||
}
|
||||
},
|
||||
{ width: '120px', prop: 'teamLeaderInfo', label: '领队信息', },
|
||||
{ width: '120px', prop: 'coachInfo', label: '教练信息', },
|
||||
{ width: '120px', prop: 'orderNo', label: '订单信息', },
|
||||
{ width: '120px', prop: 'paymentProofUrl', label: '付款凭证', },
|
||||
{
|
||||
width: '120px',
|
||||
@ -378,6 +525,52 @@ const selection = (e) => {
|
||||
selectListIds.value.push(element.id);
|
||||
});
|
||||
}
|
||||
//预览赛事编排
|
||||
const previewArrangement = async () => {
|
||||
try {
|
||||
const res = await eventarrangementPreviewPost(initParam.value.id);
|
||||
if (res.succeeded) {
|
||||
previewData.value = res.data || [];
|
||||
previewDialogVisible.value = true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('预览编排失败:', error);
|
||||
ElMessage.error('预览编排失败');
|
||||
}
|
||||
}
|
||||
|
||||
// 时间格式化
|
||||
const formatTime = (timeStr) => {
|
||||
if (!timeStr) return '';
|
||||
// 将 "30''" 格式转换为 "30''"
|
||||
return timeStr.replace("''", "''");
|
||||
}
|
||||
|
||||
// 判断是否需要合并时间列
|
||||
const isTimeMerged = (index) => {
|
||||
if (index === 0) return false;
|
||||
const current = previewData.value[index];
|
||||
const prev = previewData.value[index - 1];
|
||||
return current?.projectName === prev?.projectName && current?.timeRange === prev?.timeRange;
|
||||
}
|
||||
|
||||
// 获取时间列的合并行数
|
||||
const getTimeRowspan = (index) => {
|
||||
const current = previewData.value[index];
|
||||
if (!current) return 1;
|
||||
|
||||
let count = 1;
|
||||
for (let i = index + 1; i < previewData.value.length; i++) {
|
||||
const next = previewData.value[i];
|
||||
if (next?.projectName === current.projectName && next?.timeRange === current.timeRange) {
|
||||
count++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
//更新赛事编排
|
||||
const onSave = async () => {
|
||||
const res = await eventarrangementPut({
|
||||
@ -577,7 +770,7 @@ const downloadTemplate = async () => {
|
||||
const exportCertificate = async () => {
|
||||
try {
|
||||
const { succeeded, data, errors } = await eventscoreExportcertificatePost({
|
||||
eventId: initParam.value.id
|
||||
eventId: initParam.value.eventId
|
||||
});
|
||||
if (!succeeded) {
|
||||
ElMessage.error(errors || '导出证书失败');
|
||||
@ -604,8 +797,291 @@ const exportCertificate = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 获取参与人数的单位显示
|
||||
const getParticipantUnit = (projectType) => {
|
||||
return projectType === '团队' ? '队' : '人';
|
||||
};
|
||||
|
||||
// 暴露变量
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.arrangement-container {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.sort-hint-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 16px;
|
||||
background: #ecf5ff;
|
||||
border: 1px solid #b3d8ff;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 20px;
|
||||
color: #409eff;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.sort-hint-top .el-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.arrangement-cards-wrapper {
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.arrangement-card {
|
||||
display: flex;
|
||||
border: 2px solid #409eff;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 20px;
|
||||
overflow: hidden;
|
||||
background: #ffffff;
|
||||
cursor: move;
|
||||
transition: box-shadow 0.2s, transform 0.2s;
|
||||
}
|
||||
|
||||
.arrangement-card:hover {
|
||||
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.sort-ghost {
|
||||
opacity: 0.4;
|
||||
background: #c6e2ff !important;
|
||||
}
|
||||
|
||||
.sort-drag {
|
||||
opacity: 0.9;
|
||||
box-shadow: 0 8px 20px rgba(64, 158, 255, 0.4);
|
||||
}
|
||||
|
||||
.card-left {
|
||||
flex: 0 0 180px;
|
||||
background: #409eff;
|
||||
color: #ffffff;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.card-left .project-name {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.card-left .project-type,
|
||||
.card-left .project-duration {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.card-middle {
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
align-content: flex-start;
|
||||
border-right: 2px solid #409eff;
|
||||
}
|
||||
|
||||
.group-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
border: 1px solid #409eff;
|
||||
border-radius: 6px;
|
||||
padding: 4px 12px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.group-tag .group-name {
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.group-tag .group-count {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.card-right {
|
||||
flex: 0 0 280px;
|
||||
display: flex;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.venue-box,
|
||||
.duration-box {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 16px 8px;
|
||||
}
|
||||
|
||||
.venue-box {
|
||||
border-right: 2px solid #409eff;
|
||||
}
|
||||
|
||||
.box-label {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
margin-bottom: 16px;
|
||||
margin-top: 4px;
|
||||
text-align: center;
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
.venue-box .el-input-number {
|
||||
width: 140px;
|
||||
}
|
||||
|
||||
.venue-box :deep(.el-input__wrapper) {
|
||||
justify-content: center;
|
||||
padding: 8px 15px;
|
||||
}
|
||||
|
||||
.venue-box :deep(.el-input__inner) {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.duration-value {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
margin-top: 30px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.action-bar .el-button {
|
||||
min-width: 100px;
|
||||
height: 36px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.arrangement-card {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.card-left {
|
||||
flex: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card-middle {
|
||||
border-right: none;
|
||||
border-bottom: 2px solid #409eff;
|
||||
}
|
||||
|
||||
.card-right {
|
||||
flex: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.venue-box {
|
||||
border-right: 2px solid #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
/* 赛事编排预览样式 */
|
||||
.preview-container {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.preview-title {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
background: #ffd9b3;
|
||||
color: #333;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.preview-table-wrapper {
|
||||
overflow-x: auto;
|
||||
border: 2px solid #333;
|
||||
}
|
||||
|
||||
.preview-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.preview-table .header-row {
|
||||
background: #ccffcc;
|
||||
}
|
||||
|
||||
.preview-table th {
|
||||
padding: 12px 8px;
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
border: 1px solid #333;
|
||||
}
|
||||
|
||||
.preview-table td {
|
||||
padding: 10px 8px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
border: 1px solid #333;
|
||||
}
|
||||
|
||||
.data-row-even {
|
||||
background: #f0fff0;
|
||||
}
|
||||
|
||||
.data-row-odd {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.time-col, .time-cell {
|
||||
width: 10%;
|
||||
background: #e8f4f8;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.project-col, .project-cell {
|
||||
width: 16%;
|
||||
}
|
||||
|
||||
.group-col, .group-cell {
|
||||
width: 13%;
|
||||
}
|
||||
|
||||
.team-col, .team-cell {
|
||||
width: 22%;
|
||||
}
|
||||
|
||||
.athlete-col, .athlete-cell {
|
||||
width: 14%;
|
||||
}
|
||||
|
||||
.session-col, .session-cell {
|
||||
width: 7%;
|
||||
}
|
||||
|
||||
.venue-col, .venue-cell {
|
||||
width: 8%;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
<el-button size="small" v-auth="'integral:config:delete'" text type="danger" icon="ele-Delete"> 删除 </el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
<el-button size="small" v-auth="'integral:config:detail'" text type="primary" icon="ele-View" @click="onOpenDialog(scope.row, true)"> 查看详情 </el-button>
|
||||
</template>
|
||||
</ProTable>
|
||||
|
||||
@ -29,37 +30,34 @@
|
||||
<el-dialog :title="dialogTitle" v-model="isShowDialog" width="900px">
|
||||
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="120px" v-if="isShowDialog">
|
||||
<el-form-item label="规则名称" prop="ruleName">
|
||||
<el-input v-model="formData.ruleName" placeholder="请输入规则名称" />
|
||||
<el-input v-model="formData.ruleName" placeholder="请输入规则名称" :disabled="isDetailMode" />
|
||||
</el-form-item>
|
||||
<el-form-item label="参与积分" prop="participationPoints">
|
||||
<el-input-number v-model="formData.participationPoints" :min="0" :step="1" style="width: 100%" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="formData.remark" type="textarea" :rows="2" placeholder="可不填" />
|
||||
<el-input-number v-model="formData.participationPoints" :min="0" :step="1" style="width: 100%" :disabled="isDetailMode" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="名次积分">
|
||||
<div style="width: 100%">
|
||||
<div style="display: flex; justify-content: flex-end; margin-bottom: 10px">
|
||||
<div style="display: flex; justify-content: flex-end; margin-bottom: 10px" v-if="!isDetailMode">
|
||||
<el-button type="primary" plain icon="ele-Plus" @click="onAddDetailRow">新增区间</el-button>
|
||||
</div>
|
||||
<el-table :data="formData.details" border style="width: 100%" >
|
||||
<el-table-column label="起始排名" width="180">
|
||||
<template #default="scope">
|
||||
<el-input-number v-model="scope.row.startRank" :min="1" :step="1" />
|
||||
<el-input-number v-model="scope.row.startRank" :min="1" :step="1" :disabled="isDetailMode" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="结束排名" width="180">
|
||||
<template #default="scope">
|
||||
<el-input-number v-model="scope.row.endRank" :min="1" :step="1" />
|
||||
<el-input-number v-model="scope.row.endRank" :min="1" :step="1" :disabled="isDetailMode" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="对应积分" width="180">
|
||||
<template #default="scope">
|
||||
<el-input-number v-model="scope.row.points" :min="0" :step="1" />
|
||||
<el-input-number v-model="scope.row.points" :min="0" :step="1" :disabled="isDetailMode" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="140" align="center">
|
||||
<el-table-column label="操作" width="140" align="center" v-if="!isDetailMode">
|
||||
<template #default="scope">
|
||||
<el-button text type="danger" icon="ele-Delete" @click="onRemoveDetailRow(scope.$index)">
|
||||
删除
|
||||
@ -69,11 +67,15 @@
|
||||
</el-table>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="formData.remark" type="textarea" :rows="2" placeholder="可不填" :disabled="isDetailMode" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="onCancel">取消</el-button>
|
||||
<el-button type="primary" @click="onSubmit">确定</el-button>
|
||||
<el-button type="primary" @click="onSubmit" v-if="!isDetailMode">确定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
@ -105,6 +107,7 @@ const buildEmptyForm = () => ({
|
||||
});
|
||||
|
||||
const formData = ref(buildEmptyForm());
|
||||
const isDetailMode = ref(false);
|
||||
|
||||
const formRules = ref({
|
||||
ruleName: [{ required: true, message: '请输入规则名称', trigger: 'blur' }],
|
||||
@ -125,14 +128,15 @@ const columns = ref([
|
||||
// search: { el: 'select' },
|
||||
// },
|
||||
{ prop: 'createdTime', label: '创建时间', align: 'center' },
|
||||
{ prop: 'action', label: '操作', align: 'center', fixed: 'right', width: 220 },
|
||||
{ prop: 'action', label: '操作', align: 'center', fixed: 'right', width: 320 },
|
||||
]);
|
||||
|
||||
const isShowDialog = ref(false);
|
||||
const dialogTitle = ref('新增');
|
||||
|
||||
const onOpenDialog = async (row?: any) => {
|
||||
dialogTitle.value = row ? '编辑' : '新增';
|
||||
const onOpenDialog = async (row?: any, isDetail = false) => {
|
||||
isDetailMode.value = isDetail;
|
||||
dialogTitle.value = isDetail ? '查看详情' : row ? '编辑' : '新增';
|
||||
if (row?.id) {
|
||||
const res: any = await pointsruleDetailGet({ id: row.id });
|
||||
formData.value = {
|
||||
@ -150,7 +154,7 @@ const onOpenDialog = async (row?: any) => {
|
||||
} else {
|
||||
formData.value = buildEmptyForm();
|
||||
}
|
||||
if (!formData.value.details?.length) onAddDetailRow();
|
||||
if (!formData.value.details?.length && !isDetail) onAddDetailRow();
|
||||
isShowDialog.value = true;
|
||||
};
|
||||
|
||||
@ -162,6 +166,30 @@ const onRemoveDetailRow = (index: number) => {
|
||||
formData.value.details.splice(index, 1);
|
||||
};
|
||||
|
||||
// 验证名次范围是否重叠
|
||||
const validateRankOverlap = () => {
|
||||
const details = formData.value.details;
|
||||
if (details.length <= 1) return true;
|
||||
|
||||
for (let i = 0; i < details.length; i++) {
|
||||
const item1 = details[i];
|
||||
if (!item1.startRank || !item1.endRank) continue;
|
||||
|
||||
for (let j = i + 1; j < details.length; j++) {
|
||||
const item2 = details[j];
|
||||
if (!item2.startRank || !item2.endRank) continue;
|
||||
|
||||
// 检查是否重叠
|
||||
const isOverlap = !(item1.endRank < item2.startRank || item2.endRank < item1.startRank);
|
||||
if (isOverlap) {
|
||||
ElMessage.error(`名次范围 ${item1.startRank}-${item1.endRank} 与 ${item2.startRank}-${item2.endRank} 存在重叠,请调整!`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const onDelete = async (id: number) => {
|
||||
await pointsruleDeleteDelete({ id });
|
||||
ElMessage.success('删除成功');
|
||||
@ -185,6 +213,10 @@ const onSubmit = () => {
|
||||
ElMessage.warning('请至少配置一个排名区间');
|
||||
return;
|
||||
}
|
||||
// 验证名次范围是否重叠
|
||||
if (!validateRankOverlap()) {
|
||||
return;
|
||||
}
|
||||
const payload = {
|
||||
id: formData.value.id,
|
||||
ruleName: formData.value.ruleName,
|
||||
@ -211,6 +243,7 @@ const onSubmit = () => {
|
||||
// 取消
|
||||
const onCancel = () => {
|
||||
isShowDialog.value = false;
|
||||
isDetailMode.value = false;
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
{{ scope.$index + 1 }}
|
||||
</template>
|
||||
|
||||
<template #projectTypeId="scope">
|
||||
<template #projectType="scope">
|
||||
{{ scope.row.projectTypeName }}
|
||||
</template>
|
||||
|
||||
@ -30,9 +30,8 @@
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
|
||||
<el-button v-auth="'project:detail'" size="small" text type="primary" @click="onOpenMenu(scope.row)">
|
||||
查看详情 </el-button>
|
||||
<el-button v-auth="'project:detail'" size="small" text type="primary"
|
||||
@click="onViewDetail(scope.row)"> 查看详情 </el-button>
|
||||
</template>
|
||||
</ProTable>
|
||||
|
||||
@ -46,8 +45,8 @@
|
||||
</el-form-item>
|
||||
|
||||
<!-- 项目类型 -->
|
||||
<el-form-item label="项目类型:" prop="projectTypeId">
|
||||
<el-select v-model="formData.projectTypeId" placeholder="请选择项目类型" style="width: 100%">
|
||||
<el-form-item label="项目类型:" prop="projectType">
|
||||
<el-select v-model="formData.projectType" placeholder="请选择项目类型" style="width: 100%">
|
||||
<el-option label="个人速度" :value="1003"></el-option>
|
||||
<el-option label="个人花样" :value="1004"></el-option>
|
||||
<el-option label="团队速度" :value="1005"></el-option>
|
||||
@ -74,6 +73,8 @@
|
||||
callback(new Error('请选择出生日期开始'));
|
||||
} else if (!value.birthDateEnd) {
|
||||
callback(new Error('请选择出生日期结束'));
|
||||
} else if (new Date(value.birthDateStart) > new Date(value.birthDateEnd)) {
|
||||
callback(new Error('出生日期结束必须大于或等于出生日期开始'));
|
||||
} else if (!value.gender) {
|
||||
callback(new Error('请选择性别'));
|
||||
} else {
|
||||
@ -85,9 +86,9 @@
|
||||
<el-input v-model="group.groupName" placeholder="输入组别名"
|
||||
style="width: 100px; margin-right: 18px"></el-input>
|
||||
<el-date-picker v-model="group.birthDateStart" type="date" placeholder="出生日期开始"
|
||||
style="margin-right: 18px"></el-date-picker>
|
||||
style="margin-right: 18px" value-format="YYYY-MM-DD"></el-date-picker>
|
||||
<el-date-picker v-model="group.birthDateEnd" type="date" placeholder="出生日期结束"
|
||||
style="margin-right: 18px"></el-date-picker>
|
||||
style="margin-right: 18px" value-format="YYYY-MM-DD"></el-date-picker>
|
||||
<el-select v-model="group.gender" placeholder="性别" style="width: 100px">
|
||||
<el-option label="男" value="0"></el-option>
|
||||
<el-option label="女" value="1"></el-option>
|
||||
@ -125,6 +126,76 @@
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 详情弹窗 - 复用编辑页面布局 -->
|
||||
<el-dialog title="项目详情" v-model="isShowDetailDialog" width="1100px">
|
||||
<el-form :model="detailData" label-width="150px"
|
||||
style="width: 90%; margin-bottom: 30px" v-if="isShowDetailDialog">
|
||||
<!-- 项目名称 -->
|
||||
<el-form-item label="项目名称:">
|
||||
<el-input v-model="detailData.name" disabled></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 项目类型 -->
|
||||
<el-form-item label="项目类型:">
|
||||
<el-select v-model="detailData.projectType" disabled style="width: 100%">
|
||||
<el-option label="个人速度" :value="1003"></el-option>
|
||||
<el-option label="个人花样" :value="1004"></el-option>
|
||||
<el-option label="团队速度" :value="1005"></el-option>
|
||||
<el-option label="团队花样" :value="1006"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 完成人数 -->
|
||||
<el-form-item label="完成人数:">
|
||||
<el-input-number v-model="detailData.completionCount" disabled :min="0" :step="1" style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 组别 -->
|
||||
<div class="detail-groups-section">
|
||||
<el-form-item :label="'组别' + (index + 1) + ':'"
|
||||
v-for="(group, index) in (detailData.groups || [])" :key="index">
|
||||
<el-input v-model="group.groupName" disabled
|
||||
style="width: 100px; margin-right: 18px"></el-input>
|
||||
<el-date-picker v-model="group.birthDateStart" type="date" disabled
|
||||
style="margin-right: 18px" value-format="YYYY-MM-DD"></el-date-picker>
|
||||
<el-date-picker v-model="group.birthDateEnd" type="date" disabled
|
||||
style="margin-right: 18px" value-format="YYYY-MM-DD"></el-date-picker>
|
||||
<el-select v-model="group.gender" disabled style="width: 100px">
|
||||
<el-option label="男" value="0"></el-option>
|
||||
<el-option label="女" value="1"></el-option>
|
||||
<el-option label="不限" value="3"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
|
||||
<!-- 视觉分隔线 -->
|
||||
<div class="detail-section-divider"></div>
|
||||
|
||||
<!-- 费用 -->
|
||||
<el-form-item label="费用:">
|
||||
<el-input v-model="detailData.fee" disabled class="fee-input">
|
||||
<template slot="append">元</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 时长 -->
|
||||
<el-form-item label="时长:">
|
||||
<div class="duration-row">
|
||||
<el-input v-model="detailData.durationMinutes" disabled type="number"></el-input>
|
||||
<span class="duration-label">分</span>
|
||||
<span class="duration-separator">--</span>
|
||||
<el-input v-model="detailData.durationSeconds" disabled type="number"></el-input>
|
||||
<span class="duration-label">秒</span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="isShowDetailDialog = false">关闭</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -142,6 +213,20 @@ const initParam = ref({
|
||||
refresh: true,
|
||||
});
|
||||
|
||||
// 详情弹窗相关
|
||||
const isShowDetailDialog = ref(false);
|
||||
const detailData = ref(null);
|
||||
|
||||
// 日期格式化函数
|
||||
const formatDate = (date) => {
|
||||
if (!date) return '';
|
||||
const d = new Date(date);
|
||||
const year = d.getFullYear();
|
||||
const month = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(d.getDate()).padStart(2, '0');
|
||||
return `${year}-${month}-${day}`;
|
||||
};
|
||||
|
||||
const addGroup = () => {
|
||||
formData.value.groups.push({
|
||||
groupName: '',
|
||||
@ -157,7 +242,7 @@ const removeGroup = (index) => {
|
||||
|
||||
const formData = ref({
|
||||
name: '',
|
||||
projectTypeId: '',
|
||||
projectType: '',
|
||||
completionCount: '',
|
||||
fee: '',
|
||||
durationMinutes: '',
|
||||
@ -209,7 +294,7 @@ const validateGroups = (rule, value, callback) => {
|
||||
|
||||
const formRules = ref({
|
||||
name: [{ required: true, message: '请输入项目名称', trigger: 'blur' }],
|
||||
projectTypeId: [{ required: true, message: '请选择项目类型', trigger: 'change' }],
|
||||
projectType: [{ required: true, message: '请选择项目类型', trigger: 'change' }],
|
||||
completionCount: [{ required: true, message: '请输入完成人数', trigger: 'blur' }],
|
||||
fee: [{ required: true, message: '请输入费用', trigger: 'blur' }],
|
||||
duration: [
|
||||
@ -240,11 +325,11 @@ const columns = ref([
|
||||
},
|
||||
},
|
||||
{
|
||||
prop: 'projectTypeId',
|
||||
prop: 'projectType',
|
||||
label: '项目类型',
|
||||
search: {
|
||||
el: 'select',
|
||||
key: 'ProjectTypeId',
|
||||
key: 'ProjectType',
|
||||
},
|
||||
enum: [
|
||||
{ label: '全部', value: '' },
|
||||
@ -262,7 +347,7 @@ const columns = ref([
|
||||
|
||||
{ prop: 'time', label: '时长' },
|
||||
|
||||
{ prop: 'action', label: '操作', align: 'center', fixed: 'right', isShow: auths(['project:delete', 'project:edit', 'project:add']) },
|
||||
{ prop: 'action', label: '操作', align: 'center', fixed: 'right' },
|
||||
]);
|
||||
|
||||
const isShowDialog = ref(false);
|
||||
@ -272,7 +357,7 @@ const onOpenMenu = async (row) => {
|
||||
dialogTitle.value = row ? '编辑' : '新增';
|
||||
formData.value = {
|
||||
name: '',
|
||||
projectTypeId: '',
|
||||
projectType: '',
|
||||
completionCount: '',
|
||||
fee: '',
|
||||
durationMinutes: '',
|
||||
@ -300,13 +385,13 @@ const onOpenMenu = async (row) => {
|
||||
},
|
||||
];
|
||||
} else {
|
||||
// 过滤无效的 group 并补充缺失的属性
|
||||
// 过滤无效的 group 并补充缺失的属性,同时格式化日期
|
||||
formData.value.groups = formData.value.groups
|
||||
.filter(group => group)
|
||||
.map(group => ({
|
||||
groupName: group.groupName || '',
|
||||
birthDateStart: group.birthDateStart || '',
|
||||
birthDateEnd: group.birthDateEnd || '',
|
||||
birthDateStart: formatDate(group.birthDateStart),
|
||||
birthDateEnd: formatDate(group.birthDateEnd),
|
||||
gender: group.gender !== undefined && group.gender !== null ? group.gender.toString() : '',
|
||||
}));
|
||||
// 如果过滤后没有组,添加一个空组
|
||||
@ -325,6 +410,31 @@ const onOpenMenu = async (row) => {
|
||||
isShowDialog.value = true;
|
||||
};
|
||||
|
||||
// 查看详情
|
||||
const onViewDetail = async (row) => {
|
||||
const { succeeded, data } = await projectDetailGet({ id: row.id });
|
||||
if (succeeded) {
|
||||
// 确保 groups 数组存在且有效,并格式化日期
|
||||
let groupsData = [];
|
||||
if (data.groups && Array.isArray(data.groups)) {
|
||||
groupsData = data.groups
|
||||
.filter(group => group)
|
||||
.map(group => ({
|
||||
groupName: group.groupName || '',
|
||||
birthDateStart: formatDate(group.birthDateStart),
|
||||
birthDateEnd: formatDate(group.birthDateEnd),
|
||||
gender: group.gender !== undefined && group.gender !== null ? group.gender.toString() : '',
|
||||
}));
|
||||
}
|
||||
|
||||
detailData.value = {
|
||||
...data,
|
||||
groups: groupsData
|
||||
};
|
||||
isShowDetailDialog.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
//删除菜单
|
||||
const onDeleteMenu = async (id) => {
|
||||
const { succeeded, errors } = await projectDeleteDelete({ id });
|
||||
@ -429,4 +539,9 @@ const onCancel = () => {
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 详情页面组别区域样式 */
|
||||
.detail-groups-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -169,7 +169,12 @@ const formRules = ref({
|
||||
{ required: true, message: '请上传营业执照', trigger: 'blur' }
|
||||
],
|
||||
contactPhone: [
|
||||
{ required: true, message: '请输入联系电话', trigger: 'blur' }
|
||||
{ required: true, message: '请输入联系电话', trigger: 'blur' },
|
||||
{
|
||||
pattern: /^1[3-9]\d{9}$/,
|
||||
message: '请输入正确的手机号格式',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
<template>
|
||||
<div class="system-menu-container layout-padding">
|
||||
<ProTable ref="tableRef" :request-api="appuserPageGet" @selection-change="handleSelectionChange" :columns="columns" :pagination="true"
|
||||
:tool-button="false" :initParams="initParam" row-key="id">
|
||||
<ProTable ref="tableRef" :request-api="appuserPageGet" @selection-change="handleSelectionChange" :columns="columns" :pagination="true" :tool-button="false" :initParams="initParam" row-key="id">
|
||||
<template #tools>
|
||||
<el-popconfirm title="确认批量审核吗?" @confirm="onBatchAudit">
|
||||
<template #reference>
|
||||
<el-button v-auth="'user:general:audit'" size="small" type="primary"> 批量审核
|
||||
</el-button>
|
||||
<el-button v-auth="'user:general:audit'" size="small" type="primary"> 批量审核 </el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
@ -14,44 +12,28 @@
|
||||
{{ scope.$index + 1 }}
|
||||
</template>
|
||||
<template #auditStatus="scope">
|
||||
<el-tag :type="statusList[scope.row.auditStatus]">
|
||||
{{ statusNameList[scope.row.auditStatus] }}
|
||||
<el-tag :type="getAuditStatusType(scope.row.auditStatus)">
|
||||
{{ getAuditStatusText(scope.row.auditStatus) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #ss="scope">
|
||||
|
||||
</template>
|
||||
<template #idCardImageUrl="scope">
|
||||
<PreviewImage
|
||||
v-if="scope.row.idCardImageUrl"
|
||||
:src="scope.row.idCardImageUrl"
|
||||
width="100px"
|
||||
height="100px"
|
||||
fit="cover"
|
||||
radius="4px"
|
||||
:show-zoom-icon="true"
|
||||
/>
|
||||
<PreviewImage v-if="scope.row.idCardImageUrl" :src="scope.row.idCardImageUrl" width="100px" height="100px" fit="cover" radius="4px" :show-zoom-icon="true" />
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<template #action="scope">
|
||||
<el-button v-auth="'user:general:record'" size="small" text type="primary"
|
||||
@click="onOpenMenu(scope.row)"> 参赛记录 </el-button>
|
||||
<el-button v-auth="'user:general:record'" size="small" text type="primary" @click="onOpenMenu(scope.row)"> 参赛记录 </el-button>
|
||||
</template>
|
||||
</ProTable>
|
||||
|
||||
<!-- 弹窗 -->
|
||||
<el-dialog title="参赛记录" v-model="isShowDialog" width="80%">
|
||||
<ProTable ref="table" :request-api="eventrecordPageGet" :columns="columnsUser" :pagination="true"
|
||||
:tool-button="false" :initParams="initParamUser">
|
||||
<el-dialog title="参赛记录" v-model="isShowDialog" width="80%" @close="onDialogClose">
|
||||
<ProTable v-if="isShowDialog" ref="table" :request-api="eventrecordPageGet" :columns="columnsUser" :pagination="true" :tool-button="false" :initParam="initParamUser">
|
||||
<template #index="scope">
|
||||
{{ scope.$index + 1 }}
|
||||
</template>
|
||||
<template #startTime="scope">
|
||||
{{ scope.row.startTime }} -- {{ scope.row.endTime }}
|
||||
</template>
|
||||
<template #startTime="scope"> {{ scope.row.startTime }} -- {{ scope.row.endTime }} </template>
|
||||
<template #action="scope">
|
||||
<el-button v-auth="'user:general:record'" size="small" text type="primary"
|
||||
@click="lookBook(scope.row)"> 查看证书 </el-button>
|
||||
<el-button v-auth="'user:general:record'" size="small" text type="primary" @click="lookBook(scope.row)"> 查看证书 </el-button>
|
||||
</template>
|
||||
</ProTable>
|
||||
<template #footer>
|
||||
@ -62,11 +44,9 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 弹窗 -->
|
||||
<!-- 弹窗 -->
|
||||
<el-dialog title="查看证书" v-model="isShowBook" width="80%">
|
||||
<div>
|
||||
证书
|
||||
</div>
|
||||
<div>证书</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="onCancelB">取消</el-button>
|
||||
@ -83,8 +63,8 @@ import { ElMessage, ElForm } from 'element-plus';
|
||||
import SysMenuApi from '/@/api/SysMenuApi';
|
||||
import { auths } from '/@/utils/authFunction';
|
||||
import ProTable from '/@/components/ProTable/index.vue';
|
||||
import { appuserPageGet , appuserAuditPost} from '/@/api/match/appuser';
|
||||
import { eventrecordPageGet , eventrecordCertificatesGet} from '/@/api/match/eventRecord';
|
||||
import { appuserPageGet, appuserAuditPost } from '/@/api/match/appuser';
|
||||
import { eventrecordPageGet, eventrecordCertificatesGet } from '/@/api/match/eventRecord';
|
||||
import PreviewImage from '/@/components/PreviewImage/index.vue';
|
||||
|
||||
const initParam = ref({
|
||||
@ -92,10 +72,44 @@ const initParam = ref({
|
||||
});
|
||||
const selectedListIds = ref([]);
|
||||
const initParamUser = ref({
|
||||
appUserId: '',
|
||||
appUserId: null,
|
||||
});
|
||||
const statusList = ['info', 'warning', 'success', 'danger'];
|
||||
|
||||
const getAuditStatusText = (status) => {
|
||||
switch (status) {
|
||||
case 0:
|
||||
return '待提交';
|
||||
case 1:
|
||||
return '待审核';
|
||||
case 2:
|
||||
return '审核通过';
|
||||
case 3:
|
||||
return '审核拒绝';
|
||||
case 4:
|
||||
return '已取消';
|
||||
default:
|
||||
return status;
|
||||
}
|
||||
};
|
||||
|
||||
const getAuditStatusType = (status) => {
|
||||
switch (status) {
|
||||
case 0:
|
||||
return 'info';
|
||||
case 1:
|
||||
return 'warning';
|
||||
case 2:
|
||||
return 'success';
|
||||
case 3:
|
||||
return 'danger';
|
||||
case 4:
|
||||
return 'info';
|
||||
default:
|
||||
return 'info';
|
||||
}
|
||||
};
|
||||
|
||||
const columns = ref([
|
||||
{ prop: 'ss', type: 'selection' },
|
||||
{ prop: 'index', label: '序号', align: 'center' },
|
||||
@ -113,7 +127,7 @@ const columns = ref([
|
||||
],
|
||||
},
|
||||
{ prop: 'mobile', label: '联系电话', search: { el: 'input' } },
|
||||
{ prop: 'idCardImageUrl', label: '身份证' },
|
||||
{ prop: 'idCardImageUrl', label: '身份证', width: '130px' },
|
||||
{
|
||||
prop: 'auditStatus',
|
||||
label: '审核状态',
|
||||
@ -130,7 +144,7 @@ const columns = ref([
|
||||
},
|
||||
{
|
||||
prop: 'createdTime',
|
||||
label: '创建时间',
|
||||
label: '生成时间',
|
||||
search: {
|
||||
el: 'date-picker',
|
||||
props: { type: 'daterange' },
|
||||
@ -145,7 +159,7 @@ const handleSelectionChange = (val) => {
|
||||
selectedListIds.value = val.map((item) => item.id);
|
||||
};
|
||||
|
||||
const statusNameList = ['未提交','未审核','已通过','未通过'];
|
||||
const statusNameList = ['未提交', '未审核', '已通过', '未通过'];
|
||||
|
||||
const columnsUser = ref([
|
||||
{ prop: 'index', label: '序号', align: 'center' },
|
||||
@ -179,20 +193,37 @@ const columnsUser = ref([
|
||||
isShow: false,
|
||||
},
|
||||
|
||||
{ prop: 'action', label: '操作', align: 'center', fixed: 'right'},
|
||||
{ prop: 'action', label: '操作', align: 'center', fixed: 'right' },
|
||||
]);
|
||||
|
||||
const isShowDialog = ref(false);
|
||||
const table = ref();
|
||||
|
||||
const onOpenMenu = (row) => {
|
||||
console.log(row);
|
||||
|
||||
// 参数验证
|
||||
if (!row || !row.id) {
|
||||
ElMessage.warning('用户信息无效,无法查看参赛记录');
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof row.id !== 'number' || row.id <= 0) {
|
||||
ElMessage.warning('用户ID无效,请刷新页面后重试');
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置用户ID参数
|
||||
initParamUser.value.appUserId = row.id;
|
||||
isShowDialog.value = true;
|
||||
|
||||
// 下次打开对话框时强制刷新数据
|
||||
initParamUser.value.refresh = Date.now();
|
||||
};
|
||||
|
||||
const onBatchAudit = async () => {
|
||||
console.log(selectedListIds.value);
|
||||
const { succeeded, data } = await appuserAuditPost({ ids: selectedListIds.value , status: 2 });
|
||||
const { succeeded, data } = await appuserAuditPost({ ids: selectedListIds.value, status: 2 });
|
||||
if (succeeded) {
|
||||
ElMessage.success('操作成功');
|
||||
// initParam.value.refresh = !initParam.value.refresh;
|
||||
@ -218,6 +249,14 @@ const lookBook = async (row) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 对话框关闭时清理
|
||||
const onDialogClose = () => {
|
||||
// 清理参数,避免下次查询时使用旧参数
|
||||
initParamUser.value = {
|
||||
appUserId: null,
|
||||
refresh: Date.now(),
|
||||
};
|
||||
};
|
||||
|
||||
// 取消
|
||||
const onCancel = () => {
|
||||
|
||||
@ -1,50 +1,44 @@
|
||||
<template>
|
||||
<div class="system-menu-container layout-padding">
|
||||
<ProTable ref="tableRef" :request-api="merchantcertificationPageGet" @selection-change="handleSelectionChange" :columns="columns" :pagination="true"
|
||||
:tool-button="false" :initParams="initParam" row-key="id">
|
||||
<ProTable
|
||||
ref="tableRef"
|
||||
:request-api="merchantcertificationPageGet"
|
||||
@selection-change="handleSelectionChange"
|
||||
:columns="columns"
|
||||
:pagination="true"
|
||||
:tool-button="false"
|
||||
:initParams="initParam"
|
||||
row-key="id"
|
||||
>
|
||||
<template #tools>
|
||||
<el-button v-auth="'user:general:audit'" size="small" type="primary" @click="showAuditDialog"> 批量审核
|
||||
</el-button>
|
||||
</template>
|
||||
<el-button v-auth="'user:general:audit'" size="small" type="primary" @click="showAuditDialog"> 批量审核 </el-button>
|
||||
</template>
|
||||
<template #index="scope">
|
||||
{{ scope.$index + 1 }}
|
||||
</template>
|
||||
<template #auditStatus="scope">
|
||||
<el-tag :type="getAuditStatusType(scope.row.auditStatus)">
|
||||
{{ getAuditStatusText(scope.row.auditStatus) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<el-tag :type="getAuditStatusType(scope.row.auditStatus)">
|
||||
{{ getAuditStatusText(scope.row.auditStatus) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
<template #businessLicenseUrl="scope">
|
||||
<PreviewImage
|
||||
v-if="scope.row.businessLicenseUrl"
|
||||
:src="scope.row.businessLicenseUrl"
|
||||
width="100px"
|
||||
height="100px"
|
||||
fit="cover"
|
||||
radius="4px"
|
||||
:show-zoom-icon="true"
|
||||
/>
|
||||
<PreviewImage v-if="scope.row.businessLicenseUrl" :src="scope.row.businessLicenseUrl" width="100px" height="100px" fit="cover" radius="4px" :show-zoom-icon="true" />
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<template #action="scope">
|
||||
<el-button v-auth="'user:general:record'" size="small" text type="primary"
|
||||
@click="onOpenMenu(scope.row)"> 参赛记录 </el-button>
|
||||
<el-button v-auth="'user:general:record'" size="small" text type="primary" @click="onOpenMenu(scope.row)"> 参赛记录 </el-button>
|
||||
</template>
|
||||
</ProTable>
|
||||
|
||||
<!-- 弹窗 -->
|
||||
<el-dialog title="参赛记录" v-model="isShowDialog" width="80%">
|
||||
<ProTable ref="table" :request-api="eventrecordPageGet" :columns="columnsUser" :pagination="true"
|
||||
:tool-button="false" :initParams="initParamUser">
|
||||
<el-dialog title="参赛记录" v-model="isShowDialog" width="80%" @close="onDialogClose">
|
||||
<ProTable v-if="isShowDialog" ref="table" :request-api="eventrecordPageGet" :columns="columnsUser" :pagination="true" :tool-button="false" :initParam="initParamUser">
|
||||
<template #index="scope">
|
||||
{{ scope.$index + 1 }}
|
||||
</template>
|
||||
<template #startTime="scope">
|
||||
{{ scope.row.startTime }} -- {{ scope.row.endTime }}
|
||||
</template>
|
||||
<template #startTime="scope"> {{ scope.row.startTime }} -- {{ scope.row.endTime }} </template>
|
||||
<template #action="scope">
|
||||
<el-button v-auth="'user:general:record'" size="small" text type="primary"
|
||||
@click="lookBook(scope.row)"> 查看证书 </el-button>
|
||||
<el-button v-auth="'user:general:record'" size="small" text type="primary" @click="lookBook(scope.row)"> 查看证书 </el-button>
|
||||
</template>
|
||||
</ProTable>
|
||||
<template #footer>
|
||||
@ -55,11 +49,9 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 弹窗 -->
|
||||
<!-- 弹窗 -->
|
||||
<el-dialog title="查看证书" v-model="isShowBook" width="80%">
|
||||
<div>
|
||||
证书
|
||||
</div>
|
||||
<div>证书</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="onCancelB">取消</el-button>
|
||||
@ -96,8 +88,8 @@ import { defineAsyncComponent, ref, reactive } from 'vue';
|
||||
import { ElMessage, ElForm } from 'element-plus';
|
||||
import { auths } from '/@/utils/authFunction';
|
||||
import ProTable from '/@/components/ProTable/index.vue';
|
||||
import { eventrecordPageGet , eventrecordCertificatesGet} from '/@/api/match/eventRecord';
|
||||
import { merchantcertificationPageGet, merchantcertificationAuditPost} from '/@/api/match/merchantcertification';
|
||||
import { eventrecordPageGet, eventrecordCertificatesGet } from '/@/api/match/eventRecord';
|
||||
import { merchantcertificationPageGet, merchantcertificationAuditPost } from '/@/api/match/merchantcertification';
|
||||
import PreviewImage from '/@/components/PreviewImage/index.vue';
|
||||
|
||||
const initParam = ref({
|
||||
@ -105,7 +97,7 @@ const initParam = ref({
|
||||
});
|
||||
const selectedListIds = ref([]);
|
||||
const initParamUser = ref({
|
||||
appUserId: '',
|
||||
sysUserId: null,
|
||||
});
|
||||
const statusList = ['info', 'warning', 'success', 'danger'];
|
||||
|
||||
@ -147,11 +139,10 @@ const columns = ref([
|
||||
{ prop: 'ss', type: 'selection' },
|
||||
{ prop: 'index', label: '序号', align: 'center' },
|
||||
{ prop: 'merchantName', label: '单位名称', search: { el: 'input' } },
|
||||
{ prop: 'creditCode', label: '统一社会信用代码', search: { el: 'input' } },
|
||||
{ prop: 'address', label: '地址' },
|
||||
|
||||
|
||||
{ prop: 'contactPhone', label: '联系电话', search: { el: 'input' } },
|
||||
{prop: 'businessLicenseUrl', label: '营业执照', width: '130px',},
|
||||
{ prop: 'businessLicenseUrl', label: '营业执照', width: '130px' },
|
||||
{
|
||||
prop: 'auditStatus',
|
||||
label: '审核状态',
|
||||
@ -159,17 +150,17 @@ const columns = ref([
|
||||
el: 'select',
|
||||
},
|
||||
enum: [
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '审核拒绝', value: 3 },
|
||||
{ label: '审核通过', value: 2 },
|
||||
{ label: '待审核', value: 1 },
|
||||
{ label: '待提交', value: 0 },
|
||||
{ label: '已取消', value: 4 },
|
||||
],
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '审核拒绝', value: 3 },
|
||||
{ label: '审核通过', value: 2 },
|
||||
{ label: '待审核', value: 1 },
|
||||
{ label: '待提交', value: 0 },
|
||||
{ label: '已取消', value: 4 },
|
||||
],
|
||||
},
|
||||
{
|
||||
prop: 'createdTime',
|
||||
label: '创建时间',
|
||||
label: '生成时间',
|
||||
search: {
|
||||
el: 'date-picker',
|
||||
props: { type: 'daterange' },
|
||||
@ -216,15 +207,32 @@ const columnsUser = ref([
|
||||
isShow: false,
|
||||
},
|
||||
|
||||
{ prop: 'action', label: '操作', align: 'center', fixed: 'right'},
|
||||
{ prop: 'action', label: '操作', align: 'center', fixed: 'right' },
|
||||
]);
|
||||
|
||||
const isShowDialog = ref(false);
|
||||
const table = ref();
|
||||
|
||||
const onOpenMenu = (row) => {
|
||||
console.log(row);
|
||||
initParamUser.value.appUserId = row.userId;
|
||||
|
||||
// 参数验证
|
||||
if (!row || !row.userId) {
|
||||
ElMessage.warning('用户信息无效,无法查看参赛记录');
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof row.userId !== 'number' || row.userId <= 0) {
|
||||
ElMessage.warning('用户ID无效,请刷新页面后重试');
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置用户ID参数 - 单位用户使用 sysUserId
|
||||
initParamUser.value.sysUserId = row.userId;
|
||||
isShowDialog.value = true;
|
||||
|
||||
// 下次打开对话框时强制刷新数据
|
||||
initParamUser.value.refresh = Date.now();
|
||||
};
|
||||
|
||||
const onCancelB = () => {
|
||||
@ -246,7 +254,7 @@ const lookBook = async (row) => {
|
||||
const isShowAuditDialog = ref(false);
|
||||
const auditForm = reactive({
|
||||
status: 2, // 默认审核通过
|
||||
remark: ''
|
||||
remark: '',
|
||||
});
|
||||
|
||||
const showAuditDialog = () => {
|
||||
@ -261,10 +269,10 @@ const showAuditDialog = () => {
|
||||
|
||||
const onSubmitAudit = async () => {
|
||||
console.log(selectedListIds.value);
|
||||
const { succeeded, data } = await merchantcertificationAuditPost({
|
||||
ids: selectedListIds.value,
|
||||
const { succeeded, data } = await merchantcertificationAuditPost({
|
||||
ids: selectedListIds.value,
|
||||
status: auditForm.status,
|
||||
remark: auditForm.remark
|
||||
remark: auditForm.remark,
|
||||
});
|
||||
if (succeeded) {
|
||||
ElMessage.success('操作成功');
|
||||
@ -276,6 +284,15 @@ const onSubmitAudit = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 对话框关闭时清理
|
||||
const onDialogClose = () => {
|
||||
// 清理参数,避免下次查询时使用旧参数
|
||||
initParamUser.value = {
|
||||
sysUserId: null,
|
||||
refresh: Date.now(),
|
||||
};
|
||||
};
|
||||
|
||||
// 取消
|
||||
const onCancel = () => {
|
||||
isShowDialog.value = false;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="system-menu-container layout-padding">
|
||||
<ProTable ref="tableRef" :request-api="sysuserPageGet" :columns="columns" :pagination="true"
|
||||
:tool-button="false" :initParam="initParam">
|
||||
:tool-button="true" :initParam="initParam">
|
||||
<template #tools>
|
||||
<el-button v-auth="'permission:account:add'" type="primary" icon="ele-Plus" @click="onOpenMenu()"> 新增
|
||||
</el-button>
|
||||
@ -141,6 +141,9 @@ const columns = ref([
|
||||
prop: 'account',
|
||||
label: '账号',
|
||||
align: 'center',
|
||||
search: {
|
||||
el: 'input',
|
||||
},
|
||||
},
|
||||
{
|
||||
prop: 'roleNames',
|
||||
@ -157,7 +160,7 @@ const columns = ref([
|
||||
enum: [],
|
||||
},
|
||||
},
|
||||
{ prop: 'createdTime', label: '生成时间', search: { el: 'date-picker', props: { type: 'date' } } },
|
||||
{ prop: 'createdTime', label: '生成时间', search: { el: 'date-picker', props: { type: 'daterange' } }, minWidth: 160 },
|
||||
|
||||
{ prop: 'action', label: '操作', align: 'center', fixed: 'right', isShow: auths(['coupon:delete', 'coupon:edit']) },
|
||||
]);
|
||||
@ -226,10 +229,11 @@ const onSubmit = () => {
|
||||
ElMessage.success(dialogTitle.value + '成功');
|
||||
isShowDialog.value = false;
|
||||
} else {
|
||||
ElMessage.error(res.errors);
|
||||
ElMessage.error(res.errors || dialogTitle.value + '失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
console.error('提交失败:', error);
|
||||
// 错误已经在拦截器中显示,这里不需要再显示
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -240,10 +244,19 @@ const onCancel = () => {
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
const { data } = await sysroleSelectGet();
|
||||
console.log('获取的角色数据:', data);
|
||||
columns.value[4].search.enum = data;
|
||||
eventList.value = data;
|
||||
try {
|
||||
const result = await sysroleSelectGet();
|
||||
console.log('获取的角色数据:', result);
|
||||
if (result.succeeded && result.data) {
|
||||
columns.value[4].search.enum = result.data;
|
||||
eventList.value = result.data;
|
||||
} else {
|
||||
ElMessage.error(result.errors || '获取角色数据失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取角色数据时发生错误:', error);
|
||||
ElMessage.error('获取角色数据时发生错误,请稍后重试');
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
@ -21,11 +21,6 @@
|
||||
<el-input v-model="state.ruleForm.code" maxlength="32" placeholder="请输入角色标识" clearable></el-input>
|
||||
</el-form-item>
|
||||
</el-col> -->
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
|
||||
<el-form-item label="排序" prop="sort">
|
||||
<el-input-number v-model="state.ruleForm.sort" controls-position="right" placeholder="请输入排序" class="w100" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
|
||||
<el-form-item label="角色状态" prop="status">
|
||||
<el-switch
|
||||
@ -43,7 +38,7 @@
|
||||
<el-input v-model="state.ruleForm.remark" type="textarea" placeholder="请输入角色描述" maxlength="200"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20" v-if="state.ruleForm.id != 0">
|
||||
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
|
||||
<el-form-item label="菜单权限" prop="menus">
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
@ -86,25 +81,23 @@ const treeRef = ref();
|
||||
const rules = reactive({
|
||||
name: [{ required: true, message: '请输入角色名称' }],
|
||||
code: [{ required: true, message: '请输入角色标识' }],
|
||||
sort: [{ required: true, message: '请输入排序' }],
|
||||
// menus: [
|
||||
// {
|
||||
// validator: (rule, value, callback) => {
|
||||
// if ((value ?? []).length === 0) {
|
||||
// callback(new Error('请为角色分配权限'));
|
||||
// return;
|
||||
// }
|
||||
// callback();
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
menus: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if ((value ?? []).length === 0) {
|
||||
callback(new Error('请为角色分配权限'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
//表单状态
|
||||
const state = reactive({
|
||||
ruleForm: {
|
||||
status: 0,
|
||||
sort: 100,
|
||||
code: 10000,
|
||||
},
|
||||
menuData: [],
|
||||
@ -116,6 +109,22 @@ const state = reactive({
|
||||
},
|
||||
});
|
||||
|
||||
// 重置表单和权限选择
|
||||
const resetForm = () => {
|
||||
state.ruleForm = {
|
||||
id: 0,
|
||||
name: '',
|
||||
code: 10000,
|
||||
status: 0,
|
||||
remark: '',
|
||||
menus: [],
|
||||
};
|
||||
nextTick(() => {
|
||||
roleDialogFormRef.value?.resetFields();
|
||||
treeRef.value?.setCheckedKeys([]);
|
||||
});
|
||||
};
|
||||
|
||||
// 打开弹窗
|
||||
const openDialog = async (row) => {
|
||||
console.log(row);
|
||||
@ -123,21 +132,21 @@ const openDialog = async (row) => {
|
||||
state.dialog.loading = true;
|
||||
const { data: menus } = await sysmenuTreeselectGet();
|
||||
state.menuData = menus ?? [];
|
||||
|
||||
if (row != null) {
|
||||
state.ruleForm = { ...row };
|
||||
const { data } = await sysroleGetrolemenuGet({ id: row.id });
|
||||
state.ruleForm.menus = data ?? [];
|
||||
state.dialog.title = '修改角色';
|
||||
state.dialog.submitTxt = '修 改';
|
||||
treeRef.value?.setCheckedKeys(data ?? []);
|
||||
nextTick(() => {
|
||||
treeRef.value?.setCheckedKeys(data ?? []);
|
||||
});
|
||||
} else {
|
||||
state.ruleForm.id = 0;
|
||||
// 新增角色时完全重置表单和权限
|
||||
resetForm();
|
||||
state.dialog.title = '新增角色';
|
||||
state.dialog.submitTxt = '新 增';
|
||||
// 重置表单
|
||||
nextTick(() => {
|
||||
roleDialogFormRef.value?.resetFields();
|
||||
});
|
||||
}
|
||||
state.dialog.loading = false;
|
||||
};
|
||||
@ -151,26 +160,22 @@ const onCancel = () => {
|
||||
};
|
||||
// 提交
|
||||
const onSubmit = async () => {
|
||||
if (state.ruleForm.id != 0) {
|
||||
state.ruleForm.menus = treeRef.value.getCheckedKeys();
|
||||
}
|
||||
state.ruleForm.menus = treeRef.value?.getCheckedKeys() ?? [];
|
||||
roleDialogFormRef.value?.validate(async (v) => {
|
||||
if (v) {
|
||||
const { succeeded } = state.ruleForm.id === 0 ? await sysroleAddPost(state.ruleForm) : await sysroleEditPut(state.ruleForm);
|
||||
const { succeeded, data } = state.ruleForm.id === 0
|
||||
? await sysroleAddPost(state.ruleForm)
|
||||
: await sysroleEditPut(state.ruleForm);
|
||||
if (succeeded) {
|
||||
if (state.ruleForm.id != 0) {
|
||||
await sysroleGrantpermissionPost({
|
||||
id: state.ruleForm.id,
|
||||
menuIds: state.ruleForm.menus,
|
||||
});
|
||||
ElMessage.success('修改成功');
|
||||
closeDialog();
|
||||
emit('refresh');
|
||||
} else {
|
||||
ElMessage.success('新增成功');
|
||||
closeDialog();
|
||||
emit('refresh');
|
||||
}
|
||||
// 无论是新增还是修改,都需要授予菜单权限
|
||||
const roleId = state.ruleForm.id === 0 ? data : state.ruleForm.id;
|
||||
await sysroleGrantpermissionPost({
|
||||
id: roleId,
|
||||
menuIds: state.ruleForm.menus,
|
||||
});
|
||||
ElMessage.success(state.ruleForm.id === 0 ? '新增成功' : '修改成功');
|
||||
closeDialog();
|
||||
emit('refresh');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -35,29 +35,24 @@ const columns = reactive([
|
||||
{
|
||||
type: 'index',
|
||||
label: '序号',
|
||||
width: 60,
|
||||
},
|
||||
{
|
||||
prop: 'name',
|
||||
label: '角色名称',
|
||||
search: { el: 'input' },
|
||||
width: 200,
|
||||
},
|
||||
// {
|
||||
// prop: 'code',
|
||||
// label: '角色标识',
|
||||
// },
|
||||
{
|
||||
prop: 'sort',
|
||||
label: '排序',
|
||||
},
|
||||
{
|
||||
prop: 'status',
|
||||
label: '状态',
|
||||
},
|
||||
{
|
||||
prop: 'createdTime',
|
||||
label: '创建时间',
|
||||
prop: 'remark',
|
||||
label: '角色描述',
|
||||
showOverflowTooltip: true,
|
||||
},
|
||||
{
|
||||
prop: 'action',
|
||||
|
||||
@ -79,7 +79,7 @@ import { appuserLeaderPageGet, appuserLeaderSavePost, appuserMemberDeletePost }
|
||||
const formRef = ref();
|
||||
const initParam = ref({
|
||||
refresh: true,
|
||||
personType: 3
|
||||
PersonType: 3
|
||||
});
|
||||
|
||||
const getDefaultFormData = () => ({
|
||||
@ -109,7 +109,12 @@ const formRules = ref({
|
||||
{ required: true, message: '请选择性别', trigger: 'change' }
|
||||
],
|
||||
mobile: [
|
||||
{ required: true, message: '请输入联系电话', trigger: 'blur' }
|
||||
{ required: true, message: '请输入联系电话', trigger: 'blur' },
|
||||
{
|
||||
pattern: /^1[3-9]\d{9}$/,
|
||||
message: '请输入有效的手机号码',
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
@ -91,13 +91,13 @@ import { ElMessage, ElForm } from 'element-plus';
|
||||
import SysMenuApi from '/@/api/SysMenuApi';
|
||||
import { auths } from '/@/utils/authFunction';
|
||||
import ProTable from '/@/components/ProTable/index.vue';
|
||||
import { appuserCoachPageGet ,appuserLeaderSavePost , appuserMemberDeletePost} from '/@/api/match/appuser';
|
||||
import { appuserCoachPageGet, appuserCoachSavePost, appuserMemberDeletePost } from '/@/api/match/appuser';
|
||||
|
||||
// 定义表单引用类型
|
||||
const formRef = ref();
|
||||
const initParam = ref({
|
||||
refresh: true,
|
||||
personType:2
|
||||
PersonType: 2
|
||||
});
|
||||
const formData = ref({
|
||||
name: '',
|
||||
@ -119,7 +119,12 @@ const formRules = ref({
|
||||
{ required: true, message: '请选择性别', trigger: 'change' }
|
||||
],
|
||||
mobile: [
|
||||
{ required: true, message: '请输入联系电话', trigger: 'blur' }
|
||||
{ required: true, message: '请输入联系电话', trigger: 'blur' },
|
||||
{
|
||||
pattern: /^1[3-9]\d{9}$/,
|
||||
message: '请输入有效的手机号码',
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
],
|
||||
// idCardImageUrl: [
|
||||
// { required: true, message: '请上传身份证', trigger: 'change' }
|
||||
@ -142,8 +147,8 @@ const columns = ref([
|
||||
{ label: '女', value: 1 },
|
||||
],
|
||||
},
|
||||
{ prop: 'age', label: '年龄' , search: { el: 'input' }},
|
||||
{ prop: 'mobile', label: '联系电话' , search: { el: 'input' }},
|
||||
{ prop: 'age', label: '年龄' },
|
||||
{ prop: 'mobile', label: '联系电话', search: { el: 'input' } },
|
||||
// { prop: 'idCardImageUrl', label: '身份证' },
|
||||
|
||||
{ prop: 'action', label: '操作', align: 'center', fixed: 'right' },
|
||||
@ -201,9 +206,9 @@ const onSubmit = () => {
|
||||
try {
|
||||
let res;
|
||||
if (formData.value.id) {
|
||||
res = await appuserLeaderSavePost(formData.value);
|
||||
res = await appuserCoachSavePost(formData.value);
|
||||
} else {
|
||||
res = await appuserLeaderSavePost(formData.value);
|
||||
res = await appuserCoachSavePost(formData.value);
|
||||
}
|
||||
|
||||
if (res.succeeded) {
|
||||
|
||||
@ -103,7 +103,7 @@
|
||||
<div class="project-card" v-for="project in projects" :key="project.id">
|
||||
<div class="project-card-left">
|
||||
<div class="project-card-name">名称:{{ project.name }}</div>
|
||||
<div class="project-card-type">类型:{{ project.parentProjectTypeName ?? project.projectTypeName ?? '' }}{{ project.parentProjectTypeName || project.projectTypeName ? '-' + project.name : '' }}</div>
|
||||
<div class="project-card-type">类型:{{ project.parentProjectTypeName ?? project.projectTypeName ?? '' }}</div>
|
||||
<div class="project-card-count">完成人数:{{ project.completionCount ?? 0 }}</div>
|
||||
</div>
|
||||
<div class="project-card-right">
|
||||
@ -174,7 +174,63 @@
|
||||
</span>
|
||||
<el-icon class="assign-card-close" @click.stop="toggleGroupSelection(group.id)"><Close /></el-icon>
|
||||
</div>
|
||||
<div class="assign-card-members">
|
||||
|
||||
<!-- 团队数量信息和添加团队按钮 -->
|
||||
<div class="team-info-section" v-if="isTeamProject(group) && getGroupMaxTeams(group.id) > 1">
|
||||
<div class="team-count-info">
|
||||
已添加 {{ getGroupTeams(group.id).length }} / {{ getGroupMaxTeams(group.id) }} 队
|
||||
</div>
|
||||
<el-button
|
||||
v-if="getGroupTeams(group.id).length < getGroupMaxTeams(group.id)"
|
||||
type="primary"
|
||||
size="small"
|
||||
text
|
||||
@click.stop="addTeam(group.id)"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>
|
||||
添加团队
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 团队列表 -->
|
||||
<div v-if="isTeamProject(group) && getGroupMaxTeams(group.id) > 1" class="team-section">
|
||||
<div
|
||||
v-for="team in getGroupTeams(group.id)"
|
||||
:key="team.teamIndex"
|
||||
class="team-card"
|
||||
:class="{ 'team-card-active': activeTeamKey === `${group.id}_${team.teamIndex}` }"
|
||||
@click.stop="setActiveTeam(group.id, team.teamIndex)"
|
||||
>
|
||||
<div class="team-card-header">
|
||||
<span class="team-name">团队{{ getTeamNumber(team.teamIndex) }}</span>
|
||||
<el-button
|
||||
v-if="getGroupTeams(group.id).length > 1"
|
||||
type="danger"
|
||||
size="small"
|
||||
text
|
||||
@click.stop="removeTeam(group.id, team.teamIndex)"
|
||||
>
|
||||
<el-icon><Delete /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="team-members">
|
||||
<el-tag
|
||||
v-for="m in team.members"
|
||||
:key="m.userId ?? m.id"
|
||||
closable
|
||||
:type="getMemberValidationResult(m, group).isValid ? '' : 'danger'"
|
||||
@close.stop="removeMemberFromTeam(group.id, team.teamIndex, m.userId ?? m.id)"
|
||||
style="margin: 3px;"
|
||||
>
|
||||
{{ m.name }}
|
||||
</el-tag>
|
||||
<span v-if="!team.members.length" class="team-placeholder">选择此团队后添加队员</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 单团队模式(向后兼容) -->
|
||||
<div v-else class="assign-card-members">
|
||||
<el-tag
|
||||
v-for="m in (groupMemberMap[group.id] ?? [])"
|
||||
:key="m.userId ?? m.id"
|
||||
@ -187,9 +243,10 @@
|
||||
</el-tag>
|
||||
<span v-if="!(groupMemberMap[group.id]?.length)" class="assign-card-placeholder">点击此卡片后,从右侧选择队员</span>
|
||||
</div>
|
||||
|
||||
<!-- 队员验证错误提示 -->
|
||||
<div v-if="hasValidationErrorsForGroup(group)" class="validation-error-section">
|
||||
<div class="validation-error-item" v-for="m in (groupMemberMap[group.id] ?? [])" :key="m.userId ?? m.id">
|
||||
<div class="validation-error-item" v-for="m in (isTeamProject(group) && getGroupMaxTeams(group.id) > 1 ? getGroupTeams(group.id).flatMap(t => t.members) : (groupMemberMap[group.id] ?? []))" :key="m.userId ?? m.id">
|
||||
<el-alert
|
||||
v-if="!getMemberValidationResult(m, group).isValid"
|
||||
:title="getMemberValidationResult(m, group).message"
|
||||
@ -232,7 +289,7 @@
|
||||
:key="m.userId ?? m.id"
|
||||
class="team-member-tag"
|
||||
:class="{ 'team-member-tag-assigned': isMemberAssignedToActive(m.userId ?? m.id) }"
|
||||
@click="assignMemberToActiveGroup(m)"
|
||||
@click="assignMemberToActiveTeam(m)"
|
||||
>
|
||||
{{ m.name }}
|
||||
</div>
|
||||
@ -261,14 +318,7 @@
|
||||
style="margin-bottom: 12px;"
|
||||
/>
|
||||
|
||||
<!-- 项目完成人数提示 -->
|
||||
<el-alert
|
||||
v-if="hasProjectCompletionCountError()"
|
||||
title="项目完成人数错误"
|
||||
type="error"
|
||||
:closable="false"
|
||||
style="margin-bottom: 12px;"
|
||||
/>
|
||||
|
||||
|
||||
<!-- 队伍数量限制提示 -->
|
||||
<el-alert
|
||||
@ -288,7 +338,7 @@
|
||||
type="primary"
|
||||
@click="handleStep2Next"
|
||||
:loading="submitDraftLoading"
|
||||
:disabled="hasAnyValidationErrors() || hasProjectCompletionCountError() || hasAnyRegistrationLimit()"
|
||||
:disabled="hasAnyValidationErrors() || hasAnyRegistrationLimit()"
|
||||
>
|
||||
下一步
|
||||
</el-button>
|
||||
@ -299,9 +349,37 @@
|
||||
<div v-else-if="activeStep === 2">
|
||||
<el-skeleton :rows="10" animated v-if="previewLoading" />
|
||||
<template v-else>
|
||||
<!-- 支付二维码显示区域 -->
|
||||
<div v-if="paymentData" class="payment-qrcode-section">
|
||||
<div class="qrcode-title">请使用微信扫码支付</div>
|
||||
<!-- 银行转账信息显示区域 -->
|
||||
<div v-if="paymentData && paymentMethod === 3" class="bank-transfer-section">
|
||||
<div class="bank-transfer-title">银行卡转账</div>
|
||||
<div class="bank-transfer-content">
|
||||
<div class="info-item">
|
||||
<text class="info-label">收款商户:</text>
|
||||
<text class="info-value">上海跃动文化科技有限公司</text>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<text class="info-label">收款账号:</text>
|
||||
<text class="info-value">456498465198456261</text>
|
||||
<el-button type="primary" size="small" @click="copyAccount">复制</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="payment-amount">
|
||||
<span class="amount-label">转账金额:</span>
|
||||
<span class="amount-value">¥ {{ finalPaymentAmount }}</span>
|
||||
</div>
|
||||
<div class="payment-tip">
|
||||
<el-alert title="提示" type="info" :closable="false">
|
||||
请使用银行APP或网银进行转账,转账完成后系统会自动审核。
|
||||
</el-alert>
|
||||
</div>
|
||||
<div class="payment-actions">
|
||||
<el-button @click="cancelPayment">取消支付</el-button>
|
||||
<el-button type="primary" @click="confirmBankTransfer">确认已转账</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 支付二维码显示区域(微信/支付宝) -->
|
||||
<div v-else-if="paymentData" class="payment-qrcode-section">
|
||||
<div class="qrcode-title">请使用{{ paymentMethod === 2 ? '支付宝' : '微信'}}扫码支付</div>
|
||||
<div class="qrcode-wrapper">
|
||||
<div v-if="paymentData.qrcodeUrl" class="qrcode-container">
|
||||
<div ref="qrcodeRef"></div>
|
||||
@ -428,7 +506,7 @@
|
||||
<script setup>
|
||||
import { ref, computed, nextTick, onUnmounted } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { Close, Picture, Loading, CircleCheck, ArrowRight } from '@element-plus/icons-vue';
|
||||
import { Close, Picture, Loading, CircleCheck, ArrowRight, Plus, Delete } from '@element-plus/icons-vue';
|
||||
import ProTable from '/@/components/ProTable/index.vue';
|
||||
import { formatDate } from '/@/utils/formatTime';
|
||||
import {
|
||||
@ -442,7 +520,6 @@ import {
|
||||
usereventregistrationMemberValidatePost,
|
||||
usereventregistrationRegistrationLimitCheckPost,
|
||||
usereventregistrationGroupTeamLimitGet,
|
||||
usereventregistrationProjectCompletionCheckPost,
|
||||
usereventregistrationOrderStatusGet,
|
||||
} from '/@/api/match/usereventregistration';
|
||||
import { teamPageGet, teamDetailGet } from '/@/api/match/unitUser';
|
||||
@ -766,6 +843,7 @@ const allGroups = computed(() => {
|
||||
projectCompletionCount: p.completionCount ?? 0,
|
||||
parentProjectTypeName: p.parentProjectTypeName,
|
||||
projectTypeName: p.projectTypeName,
|
||||
projectType: p.projectType,
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -807,8 +885,19 @@ const isFreePreview = computed(() => {
|
||||
const toggleGroupSelection = (id) => {
|
||||
const idx = selectedGroupIds.value.indexOf(id);
|
||||
if (idx >= 0) {
|
||||
// 取消选中
|
||||
selectedGroupIds.value.splice(idx, 1);
|
||||
} else {
|
||||
// 选中新组别:先找出这个组别属于哪个项目,然后取消该项目的其他组别
|
||||
const clickedGroup = allGroups.value.find(g => g.id === id);
|
||||
if (clickedGroup && clickedGroup.projectId) {
|
||||
// 移除同一项目下的其他组别
|
||||
selectedGroupIds.value = selectedGroupIds.value.filter(selectedId => {
|
||||
const selectedGroup = allGroups.value.find(g => g.id === selectedId);
|
||||
return selectedGroup?.projectId !== clickedGroup.projectId;
|
||||
});
|
||||
}
|
||||
// 添加新选中的组别
|
||||
selectedGroupIds.value.push(id);
|
||||
}
|
||||
};
|
||||
@ -845,7 +934,6 @@ const selectedMemberIds = ref([]);
|
||||
const memberValidationMap = ref({}); // { groupId_memberId: { isValid: boolean, ageValid: boolean, genderValid: boolean, message: string } }
|
||||
const groupTeamLimitMap = ref({}); // { groupId: { maxTeams: number, currentTeams: number } }
|
||||
const memberRegistrationLimitMap = ref({}); // { memberId: { isLimited: boolean, message: string } }
|
||||
const projectCompletionCheckResult = ref(null); // 项目完成人数检查结果
|
||||
const registrationLimitCheckResult = ref(null); // 报名限制检查结果
|
||||
|
||||
// Step2: 团队相关
|
||||
@ -858,6 +946,14 @@ const activeAssignGroupId = ref(null); // 当前正在分配队员的组别ID
|
||||
const groupMemberMap = ref({}); // { groupId: [member, ...] }
|
||||
const usedTeamId = ref(null); // 本次报名已使用的团队ID
|
||||
|
||||
// ==================== 多团队管理相关 ====================
|
||||
// 每个组别的团队列表: { groupId: [{ teamIndex, members: [...] }, ...] }
|
||||
const groupTeamsMap = ref({});
|
||||
// 当前正在编辑的团队(组别ID + 团队索引)
|
||||
const activeTeamKey = ref(null); // 格式: "groupId_teamIndex"
|
||||
// 组别限报团队数量: { groupId: maxTeams }
|
||||
const groupMaxTeamsMap = ref({});
|
||||
|
||||
const previewLoading = ref(false);
|
||||
const previewData = ref(null);
|
||||
const paymentMethod = ref(1);
|
||||
@ -906,12 +1002,15 @@ const clearRegisterState = () => {
|
||||
memberValidationMap.value = {};
|
||||
groupTeamLimitMap.value = {};
|
||||
memberRegistrationLimitMap.value = {};
|
||||
projectCompletionCheckResult.value = null;
|
||||
registrationLimitCheckResult.value = null;
|
||||
previewLoading.value = false;
|
||||
previewData.value = null;
|
||||
paymentMethod.value = 1;
|
||||
draftRegistrationId.value = null;
|
||||
// 多团队管理相关
|
||||
groupTeamsMap.value = {};
|
||||
activeTeamKey.value = null;
|
||||
groupMaxTeamsMap.value = {};
|
||||
// 支付相关
|
||||
paymentData.value = null;
|
||||
polling.value = false;
|
||||
@ -937,6 +1036,10 @@ const resetStep2AndBeyond = () => {
|
||||
activeAssignGroupId.value = null;
|
||||
groupMemberMap.value = {};
|
||||
usedTeamId.value = null;
|
||||
// 多团队管理相关
|
||||
groupTeamsMap.value = {};
|
||||
activeTeamKey.value = null;
|
||||
groupMaxTeamsMap.value = {};
|
||||
previewData.value = null;
|
||||
previewLoading.value = false;
|
||||
draftRegistrationId.value = null;
|
||||
@ -1102,10 +1205,7 @@ const hasAnyRegistrationLimit = () => {
|
||||
return registrationLimitCheckResult.value?.hasLimit ?? false;
|
||||
};
|
||||
|
||||
// 检查是否有项目完成人数错误
|
||||
const hasProjectCompletionCountError = () => {
|
||||
return projectCompletionCheckResult.value?.hasError ?? false;
|
||||
};
|
||||
|
||||
|
||||
// 判断组别是否已达到队伍数量限制
|
||||
const isGroupAtTeamLimit = (groupId) => {
|
||||
@ -1159,8 +1259,7 @@ const assignMemberToActiveGroup = async (member) => {
|
||||
}
|
||||
|
||||
groupMemberMap.value[gId].push({ ...member, teamId: member.teamId });
|
||||
// 实时检查项目完成人数和报名限制
|
||||
await checkProjectCompletion();
|
||||
// 实时检查报名限制
|
||||
await checkRegistrationLimit();
|
||||
};
|
||||
|
||||
@ -1181,6 +1280,235 @@ const removeMemberFromGroup = (groupId, memberId) => {
|
||||
}
|
||||
};
|
||||
|
||||
// ==================== 多团队管理函数 ====================
|
||||
|
||||
// 获取阿拉伯数字表示
|
||||
const getTeamNumber = (num) => {
|
||||
return num.toString();
|
||||
};
|
||||
|
||||
// 判断项目是否为团队类型
|
||||
const isTeamProject = (group) => {
|
||||
// 优先使用 projectType 判断
|
||||
if (group?.projectType) {
|
||||
// 项目类型枚举值:
|
||||
// 1003: 个人速度
|
||||
// 1004: 个人花样
|
||||
// 1005: 团队速度
|
||||
// 1006: 团队花样
|
||||
return group.projectType === 1005 || group.projectType === 1006;
|
||||
}
|
||||
|
||||
// 备用判断:使用 projectTypeName 或 parentProjectTypeName
|
||||
if (group?.projectTypeName || group?.parentProjectTypeName) {
|
||||
const typeName = group.projectTypeName || group.parentProjectTypeName;
|
||||
return typeName && typeName.includes('团队');
|
||||
}
|
||||
|
||||
// 最后的备用判断:使用 projectTeamSize
|
||||
if (group?.projectTeamSize) {
|
||||
return group.projectTeamSize > 1;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// 初始化组别团队数据
|
||||
const initGroupTeams = (groupId) => {
|
||||
if (!groupTeamsMap.value[groupId]) {
|
||||
groupTeamsMap.value[groupId] = [
|
||||
{
|
||||
teamIndex: 1,
|
||||
members: []
|
||||
}
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
// 获取某个组别的团队列表
|
||||
const getGroupTeams = (groupId) => {
|
||||
initGroupTeams(groupId);
|
||||
return groupTeamsMap.value[groupId];
|
||||
};
|
||||
|
||||
// 获取某个组别的限报团队数量
|
||||
const getGroupMaxTeams = (groupId) => {
|
||||
return groupMaxTeamsMap.value[groupId] ?? 1;
|
||||
};
|
||||
|
||||
// 添加团队
|
||||
const addTeam = (groupId) => {
|
||||
initGroupTeams(groupId);
|
||||
const teams = groupTeamsMap.value[groupId];
|
||||
const maxTeams = getGroupMaxTeams(groupId);
|
||||
|
||||
if (teams.length >= maxTeams) {
|
||||
ElMessage.warning(`已达到该项目组别限报${maxTeams}队的上限`);
|
||||
return;
|
||||
}
|
||||
|
||||
const newTeamIndex = teams.length + 1;
|
||||
teams.push({
|
||||
teamIndex: newTeamIndex,
|
||||
members: []
|
||||
});
|
||||
|
||||
ElMessage.success(`已添加团队${getTeamNumber(newTeamIndex)}`);
|
||||
};
|
||||
|
||||
// 删除团队
|
||||
const removeTeam = async (groupId, teamIndex) => {
|
||||
initGroupTeams(groupId);
|
||||
const teams = groupTeamsMap.value[groupId];
|
||||
|
||||
if (teams.length <= 1) {
|
||||
ElMessage.warning('至少需要保留一个团队');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要删除团队${getTeamNumber(teamIndex)}吗?该团队的队员也会被移除。`,
|
||||
'删除确认',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
);
|
||||
|
||||
const teamIdx = teams.findIndex(t => t.teamIndex === teamIndex);
|
||||
if (teamIdx > -1) {
|
||||
teams.splice(teamIdx, 1);
|
||||
|
||||
// 重新编号团队索引
|
||||
teams.forEach((team, idx) => {
|
||||
team.teamIndex = idx + 1;
|
||||
});
|
||||
|
||||
ElMessage.success('团队删除成功');
|
||||
}
|
||||
} catch {
|
||||
// 用户取消
|
||||
}
|
||||
};
|
||||
|
||||
// 获取当前激活的团队(如果启用了多团队模式)
|
||||
const getActiveTeamMembers = (groupId) => {
|
||||
// 向后兼容:如果没有启用多团队,使用原来的 groupMemberMap
|
||||
if (!groupTeamsMap.value[groupId] || groupTeamsMap.value[groupId].length === 0) {
|
||||
return groupMemberMap.value[groupId] ?? [];
|
||||
}
|
||||
|
||||
// 多团队模式:找到当前激活的团队
|
||||
if (!activeTeamKey.value) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const [activeGroupId, activeTeamIdx] = activeTeamKey.value.split('_').map(Number);
|
||||
if (activeGroupId !== groupId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const team = groupTeamsMap.value[groupId].find(t => t.teamIndex === activeTeamIdx);
|
||||
return team?.members ?? [];
|
||||
};
|
||||
|
||||
// 设置当前激活的团队
|
||||
const setActiveTeam = (groupId, teamIndex) => {
|
||||
activeTeamKey.value = `${groupId}_${teamIndex}`;
|
||||
};
|
||||
|
||||
// 将成员分配到当前激活的团队
|
||||
const assignMemberToActiveTeam = async (member) => {
|
||||
const gId = activeAssignGroupId.value;
|
||||
if (!gId) {
|
||||
ElMessage.warning('请先点击左侧的组别卡片');
|
||||
return;
|
||||
}
|
||||
|
||||
initGroupTeams(gId);
|
||||
|
||||
// 获取当前组别信息
|
||||
const group = selectedGroupItems.value.find(g => g.id === gId);
|
||||
// 检查是否为团队项目且启用了多团队模式
|
||||
const isMultiTeamMode = group && isTeamProject(group) &&
|
||||
(groupTeamsMap.value[gId].length > 1 || getGroupMaxTeams(gId) > 1);
|
||||
|
||||
if (!isMultiTeamMode) {
|
||||
// 单团队模式,使用原来的逻辑
|
||||
await assignMemberToActiveGroup(member);
|
||||
return;
|
||||
}
|
||||
|
||||
// 多团队模式
|
||||
if (!activeTeamKey.value) {
|
||||
ElMessage.warning('请先选择要分配队员的团队');
|
||||
return;
|
||||
}
|
||||
|
||||
const [activeGroupId, activeTeamIdx] = activeTeamKey.value.split('_').map(Number);
|
||||
if (activeGroupId !== gId) {
|
||||
ElMessage.warning('请先选择对应组别的团队');
|
||||
return;
|
||||
}
|
||||
|
||||
const team = groupTeamsMap.value[gId].find(t => t.teamIndex === activeTeamIdx);
|
||||
if (!team) {
|
||||
return;
|
||||
}
|
||||
|
||||
const memberId = member.userId ?? member.id;
|
||||
// 已存在则不重复添加
|
||||
if (team.members.some((m) => (m.userId ?? m.id) === memberId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查队员是否来自已选择的团队
|
||||
if (usedTeamId.value && member.teamId !== usedTeamId.value) {
|
||||
ElMessage.warning('本次报名只能选择同一个团队的队员');
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取当前组别信息进行验证(group变量已在函数开头声明)
|
||||
if (group) {
|
||||
const validation = await validateMemberForGroup(member, group);
|
||||
if (!validation.isValid) {
|
||||
ElMessage.warning(validation.message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 设置已使用的团队
|
||||
if (!usedTeamId.value) {
|
||||
usedTeamId.value = member.teamId;
|
||||
}
|
||||
|
||||
team.members.push({ ...member, teamId: member.teamId });
|
||||
// 实时检查报名限制
|
||||
await checkRegistrationLimit();
|
||||
};
|
||||
|
||||
// 从团队中移除成员
|
||||
const removeMemberFromTeam = (groupId, teamIndex, memberId) => {
|
||||
initGroupTeams(groupId);
|
||||
const team = groupTeamsMap.value[groupId].find(t => t.teamIndex === teamIndex);
|
||||
if (!team) return;
|
||||
|
||||
team.members = team.members.filter((m) => (m.userId ?? m.id) !== memberId);
|
||||
|
||||
// 检查是否还有队员
|
||||
let hasMembers = false;
|
||||
groupTeamsMap.value[groupId].forEach(t => {
|
||||
if (t.members.length > 0) {
|
||||
hasMembers = true;
|
||||
}
|
||||
});
|
||||
if (!hasMembers) {
|
||||
usedTeamId.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
// 验证优惠券
|
||||
const validateCoupon = async () => {
|
||||
if (!couponCode.value?.trim()) {
|
||||
@ -1265,38 +1593,24 @@ const fetchGroupTeamLimit = async () => {
|
||||
const { succeeded, data } = await usereventregistrationGroupTeamLimitGet(currentEventId.value);
|
||||
if (succeeded && data) {
|
||||
const map = {};
|
||||
const maxTeamsMap = {};
|
||||
data.forEach(item => {
|
||||
map[item.groupId] = {
|
||||
maxTeams: item.maxTeams,
|
||||
currentTeams: item.currentTeams
|
||||
};
|
||||
// 同时设置限报团队数量
|
||||
maxTeamsMap[item.groupId] = item.maxTeams ?? 1;
|
||||
});
|
||||
groupTeamLimitMap.value = map;
|
||||
groupMaxTeamsMap.value = maxTeamsMap;
|
||||
}
|
||||
} catch {
|
||||
console.error('加载队伍限制配置失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 检查项目完成人数
|
||||
const checkProjectCompletion = async () => {
|
||||
if (!currentEventId.value) return;
|
||||
const groupMembers = selectedGroupIds.value.map((gId) => ({
|
||||
groupId: gId,
|
||||
memberIds: (groupMemberMap.value[gId] ?? []).map((m) => m.userId ?? m.id),
|
||||
}));
|
||||
try {
|
||||
const { succeeded, data } = await usereventregistrationProjectCompletionCheckPost({
|
||||
eventId: currentEventId.value,
|
||||
groupMembers
|
||||
});
|
||||
if (succeeded) {
|
||||
projectCompletionCheckResult.value = data;
|
||||
}
|
||||
} catch {
|
||||
console.error('检查项目完成人数失败');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 检查报名限制
|
||||
const checkRegistrationLimit = async () => {
|
||||
@ -1332,22 +1646,24 @@ const handleStep1Next = async () => {
|
||||
await fetchTeamList();
|
||||
// 加载队伍限制配置
|
||||
await fetchGroupTeamLimit();
|
||||
|
||||
// 初始化每个组别的团队数据
|
||||
await nextTick();
|
||||
selectedGroupItems.value.forEach(group => {
|
||||
initGroupTeams(group.id);
|
||||
// 默认激活第一个团队
|
||||
if (group.id === activeAssignGroupId.value) {
|
||||
setActiveTeam(group.id, 1);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleStep2Next = async () => {
|
||||
if (!currentEventId.value) return;
|
||||
|
||||
// 先检查项目完成人数和报名限制
|
||||
await checkProjectCompletion();
|
||||
// 先检查报名限制
|
||||
await checkRegistrationLimit();
|
||||
|
||||
// 检查是否有项目完成人数错误
|
||||
if (hasProjectCompletionCountError()) {
|
||||
const errors = projectCompletionCheckResult.value?.errors ?? [];
|
||||
ElMessage.error(errors[0]?.errorMessage ?? '项目完成人数有错误,请检查');
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否有报名限制
|
||||
if (hasAnyRegistrationLimit()) {
|
||||
const details = registrationLimitCheckResult.value?.limitDetails ?? [];
|
||||
@ -1355,8 +1671,30 @@ const handleStep2Next = async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查每个组别是否至少分配了一个队员
|
||||
const emptyGroups = selectedGroupItems.value.filter((g) => !(groupMemberMap.value[g.id]?.length));
|
||||
// 检查每个组别是否至少分配了一个队员(考虑多团队模式)
|
||||
const emptyGroups = [];
|
||||
for (const g of selectedGroupItems.value) {
|
||||
const gId = g.id;
|
||||
let hasMembers = false;
|
||||
|
||||
// 检查是否为团队项目且启用了多团队模式
|
||||
if (isTeamProject(g) && groupTeamsMap.value[gId] && groupTeamsMap.value[gId].length > 0) {
|
||||
for (const team of groupTeamsMap.value[gId]) {
|
||||
if (team.members.length > 0) {
|
||||
hasMembers = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 单团队模式
|
||||
hasMembers = !!(groupMemberMap.value[gId]?.length);
|
||||
}
|
||||
|
||||
if (!hasMembers) {
|
||||
emptyGroups.push(g);
|
||||
}
|
||||
}
|
||||
|
||||
if (emptyGroups.length > 0) {
|
||||
ElMessage.warning(`请为所有组别分配队员,"${emptyGroups[0].groupName ?? emptyGroups[0].name}"尚未分配`);
|
||||
return;
|
||||
@ -1369,14 +1707,56 @@ const handleStep2Next = async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// 构造按组别分配的数据
|
||||
const groupMembers = selectedGroupIds.value.map((gId) => ({
|
||||
groupId: gId,
|
||||
memberIds: (groupMemberMap.value[gId] ?? []).map((m) => m.userId ?? m.id),
|
||||
}));
|
||||
|
||||
// 收集所有不重复的 memberIds
|
||||
const allMemberIds = [...new Set(groupMembers.flatMap((g) => g.memberIds))];
|
||||
// 构造按组别分配的数据(支持多团队)
|
||||
const groupMembers = [];
|
||||
const allTeams = []; // 用于收集所有团队数据
|
||||
const allMemberIdsSet = new Set();
|
||||
|
||||
for (const gId of selectedGroupIds.value) {
|
||||
// 找到当前组别信息
|
||||
const group = selectedGroupItems.value.find(g => g.id === gId);
|
||||
// 检查是否为团队项目且启用了多团队模式
|
||||
const isMultiTeamMode = group && isTeamProject(group) && groupTeamsMap.value[gId] &&
|
||||
(groupTeamsMap.value[gId].length > 1 || getGroupMaxTeams(gId) > 1);
|
||||
|
||||
if (isMultiTeamMode) {
|
||||
// 多团队模式
|
||||
const teams = groupTeamsMap.value[gId];
|
||||
const groupTeamsData = [];
|
||||
|
||||
for (const team of teams) {
|
||||
const memberIds = team.members.map(m => m.userId ?? m.id);
|
||||
memberIds.forEach(id => allMemberIdsSet.add(id));
|
||||
|
||||
groupTeamsData.push({
|
||||
teamIndex: team.teamIndex,
|
||||
memberIds: memberIds
|
||||
});
|
||||
}
|
||||
|
||||
groupMembers.push({
|
||||
groupId: gId,
|
||||
memberIds: groupTeamsData.flatMap(t => t.memberIds), // 保持向后兼容
|
||||
teams: groupTeamsData
|
||||
});
|
||||
|
||||
allTeams.push(...groupTeamsData.map(t => ({
|
||||
groupId: gId,
|
||||
...t
|
||||
})));
|
||||
} else {
|
||||
// 单团队模式(保持向后兼容)
|
||||
const memberIds = (groupMemberMap.value[gId] ?? []).map((m) => m.userId ?? m.id);
|
||||
memberIds.forEach(id => allMemberIdsSet.add(id));
|
||||
|
||||
groupMembers.push({
|
||||
groupId: gId,
|
||||
memberIds: memberIds
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const allMemberIds = [...allMemberIdsSet];
|
||||
|
||||
submitDraftLoading.value = true;
|
||||
try {
|
||||
@ -1513,6 +1893,47 @@ const refreshPayment = async () => {
|
||||
await handleCreateOrderAndPay();
|
||||
};
|
||||
|
||||
// 复制银行账号
|
||||
const copyAccount = () => {
|
||||
navigator.clipboard.writeText('456498465198456261').then(() => {
|
||||
ElMessage.success('账号已复制');
|
||||
}).catch(() => {
|
||||
// 如果clipboard API不可用,使用备用方法
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = '456498465198456261';
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
ElMessage.success('账号已复制');
|
||||
} catch (err) {
|
||||
ElMessage.error('复制失败,请手动复制');
|
||||
}
|
||||
document.body.removeChild(textArea);
|
||||
});
|
||||
};
|
||||
|
||||
// 确认银行转账
|
||||
const confirmBankTransfer = () => {
|
||||
ElMessageBox.confirm(
|
||||
'确认已完成转账吗?转账完成后订单将进入待审核状态。',
|
||||
'确认转账',
|
||||
{
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
).then(async () => {
|
||||
ElMessage.success('订单已提交,等待审核');
|
||||
initParam.value.refresh = !initParam.value.refresh;
|
||||
setTimeout(() => {
|
||||
isShowRegisterDialog.value = false;
|
||||
}, 1500);
|
||||
}).catch(() => {
|
||||
// 用户取消
|
||||
});
|
||||
};
|
||||
|
||||
const handleCreateOrderAndPay = async () => {
|
||||
if (!draftRegistrationId.value) return;
|
||||
createOrderLoading.value = true;
|
||||
@ -1537,7 +1958,13 @@ const handleCreateOrderAndPay = async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// 付费项目,显示二维码
|
||||
// 如果是银行转账,直接显示银行信息,不需要二维码和轮询
|
||||
if (paymentMethod.value === 3) {
|
||||
paymentData.value = data;
|
||||
return;
|
||||
}
|
||||
|
||||
// 微信/支付宝支付,显示二维码
|
||||
paymentData.value = data;
|
||||
await generateQRCode(data.qrcodeUrl);
|
||||
|
||||
@ -1769,6 +2196,74 @@ const handleCreateOrderAndPay = async () => {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ---- 团队管理样式 ---- */
|
||||
.team-info-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 14px;
|
||||
background: #f0f7ff;
|
||||
border-bottom: 1px solid #e6f4ff;
|
||||
}
|
||||
|
||||
.team-count-info {
|
||||
font-size: 13px;
|
||||
color: #409eff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.team-section {
|
||||
padding: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.team-card {
|
||||
border: 1px solid #e6f4ff;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
border-color: #56b4f9;
|
||||
}
|
||||
|
||||
&-active {
|
||||
border-color: #409eff;
|
||||
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.team-card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 6px 10px;
|
||||
background: #ecf5ff;
|
||||
border-bottom: 1px solid #d9ecff;
|
||||
}
|
||||
|
||||
.team-name {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.team-members {
|
||||
padding: 8px 10px;
|
||||
min-height: 40px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.team-placeholder {
|
||||
color: #c0c4cc;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.team-members-panel {
|
||||
flex: 1;
|
||||
border: 1px solid #d0e4f7;
|
||||
@ -1899,6 +2394,55 @@ const handleCreateOrderAndPay = async () => {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* 银行转账信息显示区域样式 */
|
||||
.bank-transfer-section {
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.bank-transfer-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.bank-transfer-content {
|
||||
background: #f5f7fa;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.bank-transfer-content .info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.bank-transfer-content .info-item:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.bank-transfer-content .info-label {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.bank-transfer-content .info-value {
|
||||
font-size: 16px;
|
||||
color: #303133;
|
||||
font-weight: 600;
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
.payment-tip {
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
.free-project-hint {
|
||||
margin-top: 16px;
|
||||
padding: 12px 16px;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user